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.
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.