Spring Data Jpa type safe Typed Sort

Spring Data Jpa provides Sort class which has a lot of sorting methods, but it has a drawback. Sort class validates the sorting parameter at run time. A developer might provide a sort on parameter which is invalid or no more in the domain class, in this case the application will throw error at run time.

package com.sujan.example.jpa.entity;

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@NoArgsConstructor
public class Apple {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long appleId;
    private String appleName;
}
package com.sujan.example.jpa.controller;

import com.sujan.example.jpa.entity.Apple;
import com.sujan.example.jpa.repository.AppleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class AppleController {
    @Autowired
    private AppleRepository appleRepository;

    @GetMapping
    List<Apple> findAllTypedSort() {
 List<Apple> all = appleRepository.findAll(Sort.by("appleName").ascending().and(Sort.by("taste").descending()));
        all.forEach(System.out::println);
        return all;
    }
}

above code sorts on the taste parameter in Descending order, but taste variable doesn't exist in domain object any more. So at run time.

PropertyReferenceException: No property taste found for type Apple!

To avoid this Spring Data Jpa provides type safe TypedSort class. TypedSort is static class defined in Sort class. TypedSort checks the parameter at compile time hence remove the chances of run time error.

drop database if exists AppleDb;
create database AppleDb;
use AppleDb;
create table apple ( apple_id bigint not null, apple_name varchar(255) default null, taste varchar(255) default null, primary key (apple_id)) engine=innodb;
create table hibernate_sequence (next_val bigint) engine=innodb;
insert into hibernate_sequence values ( 1 );
insert into apple (apple_id, apple_name, taste) values (1, "Macintosh", "tangy");
insert into apple (apple_id, apple_name, taste) values (2, "Macintosh", "sweet");
insert into apple (apple_id, apple_name, taste) values (3, "Macintosh", "juicy");
insert into apple (apple_id, apple_name, taste) values (4, "Macintosh", "tart");
insert into apple (apple_id, apple_name, taste) values (5, "Fuji", "tangy");
insert into apple (apple_id, apple_name, taste) values (6, "Fuji", "sweet");
insert into apple (apple_id, apple_name, taste) values (7, "Fuji", "juicy");
insert into apple (apple_id, apple_name, taste) values (8, "Fuji", "tart");
insert into apple (apple_id, apple_name, taste) values (9, "Gala", "tangy");
insert into apple (apple_id, apple_name, taste) values (10, "Gala", "sweet");
insert into apple (apple_id, apple_name, taste) values (11, "Gala", "juicy");
insert into apple (apple_id, apple_name, taste) values (12, "Gala", "tart");
insert into apple (apple_id, apple_name, taste) values (13, "Jonagold", "tangy");
insert into apple (apple_id, apple_name, taste) values (14, "Jonagold", "sweet");
insert into apple (apple_id, apple_name, taste) values (15, "Jonagold", "juicy");
insert into apple (apple_id, apple_name, taste) values (16, "Jonagold", "tart");
package com.sujan.example.jpa.controller;

import com.sujan.example.jpa.entity.Apple;
import com.sujan.example.jpa.repository.AppleRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class AppleController {
    @Autowired
    private AppleRepository appleRepository;

    @GetMapping
    List<Apple> findAllTypedSort() {
        Sort.TypedSort<Apple> person = Sort.sort(Apple.class);
        Sort sort = person.by(Apple::getAppleName).ascending().and(person.by(Apple::getTaste).descending());
        List<Apple> all = appleRepository.findAll(sort);
        all.forEach(System.out::println);
        return all;
    }
}
package com.sujan.example.jpa.entity;

import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Data
@Entity
@NoArgsConstructor
public class Apple {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long appleId;
    private String appleName;
    private String taste;
}
package com.sujan.example.jpa;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class JpaApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaApplication.class, args);
    }
}
spring.datasource.url=jdbc:mysql://localhost:3306/AppleDb
spring.datasource.username=root
spring.datasource.password=root
plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}
group = 'com.sujan'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}
repositories {
    mavenCentral()
}
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    annotationProcessor 'org.projectlombok:lombok'
    compileOnly 'org.projectlombok:lombok'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'mysql:mysql-connector-java'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
}
test {
    useJUnitPlatform()
}
select apple0_.apple_id as apple_id1_0_, apple0_.apple_name as apple_na2_0_, apple0_.taste as taste3_0_ from apple apple0_ order by apple0_.apple_name asc, apple0_.taste desc
Apple(appleId=8, appleName=Fuji, taste=tart)
Apple(appleId=5, appleName=Fuji, taste=tangy)
Apple(appleId=6, appleName=Fuji, taste=sweet)
Apple(appleId=7, appleName=Fuji, taste=juicy)
Apple(appleId=12, appleName=Gala, taste=tart)
Apple(appleId=9, appleName=Gala, taste=tangy)
Apple(appleId=10, appleName=Gala, taste=sweet)
Apple(appleId=11, appleName=Gala, taste=juicy)
Apple(appleId=16, appleName=Jonagold, taste=tart)
Apple(appleId=13, appleName=Jonagold, taste=tangy)
Apple(appleId=14, appleName=Jonagold, taste=sweet)
Apple(appleId=15, appleName=Jonagold, taste=juicy)
Apple(appleId=4, appleName=Macintosh, taste=tart)
Apple(appleId=1, appleName=Macintosh, taste=tangy)
Apple(appleId=2, appleName=Macintosh, taste=sweet)
Apple(appleId=3, appleName=Macintosh, taste=juicy)

 

follow us on