I use Spring Boot 1.5.3.RELEASE
and for me it's unclear how to sort by properties of nested objects with distinct
and Specifications
because of:
Caused by: org.postgresql.util.PSQLException: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
Spring Data JPA generates wrong query.
Let's see a little example:
@Data
@Entity
@Table(name = "vehicle")
public class Vehicle implements Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "vehicle_type_id")
private VehicleType vehicleType;
@ManyToOne
@JoinColumn(name = "vehicle_brand_id")
private VehicleBrand vehicleBrand;
}
We have Vehicle
class with nested objects VehicleType
and VehicleBrand
.
@Data
@Entity
@Table(name = "vehicle_brand")
public class VehicleBrand implements Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
@Column(name = "name")
private String name;
@ManyToOne
@JoinColumn(name = "vehicle_model_id")
private VehicleModel model;
}
Class VehicleBrand
also contains VehicleModel
.
@Data
@Entity
@Table(name = "vehicle_model")
public class VehicleModel implements Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
private Long id;
@Column(name = "name")
private String name;
}
Now I want to create a query with JPA Specifications
and some sorting by "vehicleBrand.name"
:
public List<Vehicle> findAll() {
Specification<Vehicle> spec = Specifications.where(
(root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
return null;
}
);
return vehicleRepository.findAll(spec, new Sort("vehicleBrand.name"));
}
Spring Data JPA generates following query:
select
distinct vehicle0_.id as id1_0_,
vehicle0_.gas_type as gas_type2_0_,
vehicle0_.vehicle_brand_id as vehicle_4_0_,
vehicle0_.vehicle_type_id as vehicle_5_0_,
vehicle0_.year_of_issue as year_of_3_0_
from
vehicle vehicle0_
left outer join
vehicle_brand vehiclebra1_
on vehicle0_.vehicle_brand_id=vehiclebra1_.id
order by
vehiclebra1_.name asc
And it fairly doesn't work because of:
Order by expression "VEHICLEBRA1_.NAME" must be in the result list in this case; SQL statement
To fix the issue we have to fetch vehicleBrand
in our Specification
:
public List<Vehicle> findAll() {
Specification<Vehicle> spec = Specifications.where(
(root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
root.fetch("vehicleBrand", JoinType.LEFT); //note that JoinType.INNER doesn't work in that case
return null;
}
);
return vehicleRepository.findAll(spec, new Sort("vehicleBrand.name"));
}
Spring Data JPA generates following query:
select
distinct vehicle0_.id as id1_0_0_,
vehiclebra1_.id as id1_1_1_,
vehicle0_.gas_type as gas_type2_0_0_,
vehicle0_.vehicle_brand_id as vehicle_4_0_0_,
vehicle0_.vehicle_type_id as vehicle_5_0_0_,
vehicle0_.year_of_issue as year_of_3_0_0_,
vehiclebra1_.vehicle_model_id as vehicle_3_1_1_,
vehiclebra1_.name as name2_1_1_
from
vehicle vehicle0_
left outer join
vehicle_brand vehiclebra1_
on vehicle0_.vehicle_brand_id=vehiclebra1_.id
order by
vehiclebra1_.name asc
And now it works because we see vehiclebra1_.name
in the selection part.
But what to do If I need to sort by "vehicleBrand.model.name"
?
I make an additional fetch
, but it doesn't work:
public List<Vehicle> findAll() {
Specification<Vehicle> spec = Specifications.where(
(root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
root.fetch("vehicleBrand", JoinType.LEFT).fetch("model", JoinType.LEFT);
return null;
}
);
return vehicleRepository.findAll(spec, new Sort("vehicleBrand.model.name"));
}
It generates following query:
select
distinct vehicle0_.id as id1_0_0_,
vehiclebra1_.id as id1_1_1_,
vehiclemod2_.id as id1_2_2_,
vehicle0_.gas_type as gas_type2_0_0_,
vehicle0_.vehicle_brand_id as vehicle_4_0_0_,
vehicle0_.vehicle_type_id as vehicle_5_0_0_,
vehicle0_.year_of_issue as year_of_3_0_0_,
vehiclebra1_.vehicle_model_id as vehicle_3_1_1_,
vehiclebra1_.name as name2_1_1_,
vehiclemod2_.name as name2_2_2_
from
vehicle vehicle0_
left outer join
vehicle_brand vehiclebra1_
on vehicle0_.vehicle_brand_id=vehiclebra1_.id
left outer join
vehicle_model vehiclemod2_
on vehiclebra1_.vehicle_model_id=vehiclemod2_.id cross
join
vehicle_model vehiclemod4_
where
vehiclebra1_.vehicle_model_id=vehiclemod4_.id
order by
vehiclemod4_.name asc
And it doesn't work because of:
Order by expression "VEHICLEMOD4_.NAME" must be in the result list in this case; SQL statement
Take a look on how we select vehiclemod2_.name
but make order by vehiclemod4_.name
.
I've tried to make sorting in Specification
directly but it also doesn't work:
Specification<Vehicle> spec = Specifications.where(
(root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
root.fetch("vehicleBrand", JoinType.LEFT).fetch("model", JoinType.LEFT);
criteriaQuery.orderBy(criteriaBuilder.asc(root.join("vehicleBrand", JoinType.LEFT).join("model", JoinType.LEFT).get("name")));
return null;
}
);
What should I do to make JPA generate right query so I could make a sorting by nested objects?
Does it make sense to upgrade version of Spring Boot from 1.5.3.RELEASE
to 2+
?
Thanks.
You can add the DISTINCT keyword to your query to tell Hibernate to return each Author entity only once. But as you can see in the following log messages, Hibernate also adds the DISTINCT keyword to the SQL query.
Spring Data JPA allows you to add a special Sort parameter to your query method. The Sort class is just a specification that provides sorting options for database queries. By using dynamic sorting, you can choose the sorting column and direction at runtime to sort the query results.
With JPA Criteria – the orderBy method is a “one stop” alternative to set all sorting parameters: both the order direction and the attributes to sort by can be set. Following is the method's API: orderBy(CriteriaBuilder. asc): Sorts in ascending order.
To allow your query methods to be transactional simply use @Transactional at the repository interface you define.
Sorting With JPA Criteria Query Object API With JPA Criteria – the orderBy method is a “one stop” alternative to set all sorting parameters: both the order direction and the attributes to sort by can be set. Following is the method's API: orderBy (CriteriaBuilder.asc): Sorts in ascending order.
DISTINCT with JPQL entity queries. The DISTINCT keyword has a different purpose when it comes to entity queries. Without using DISTINCT, the JPA specification states that the returning entities resulting from a parent-child JOIN might contain object reference duplicates.
Specification & Predicate: Advance Search and Filtering in JPA In this tutorial, you will learn how to use specification and predicate in Spring Data JPA using the Spring Boot RESTful API project. Spring Data JPA Specifications allow us to create dynamic database queries by using the JPA Criteria API.
It defines a specification as a predicate over an entity. Spring has a wrapper around the JPA criteria API (that uses predicates) and is called the specification API. Spring Data JPA repository abstraction allows executing predicates via JPA Criteria API predicates wrapped into a Specification object.
Here's a little secret: you don't need to use the Sort
parameter at all.
Just use CriteriaQuery.orderBy
:
Specification<Vehicle> spec = Specifications.where(
(root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
var model = root.fetch("vehicleBrand", JoinType.LEFT).fetch("model", JoinType.LEFT);
criteriaQuery.orderBy(criteriaBuilder.asc(model.get("name"));
return null;
}
);
return vehicleRepository.findAll(spec));
The Sort
parameter is likely what's adding the extra join in your scenario.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With