Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sort by fetched properties with distinct JPA Specifications

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:

Model

@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;
}

Service

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.

Question

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.

like image 797
Roman Danilov Avatar asked Apr 06 '21 19:04

Roman Danilov


People also ask

Can we use distinct in JPQL?

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.

Is it possible to define a sort type in @query annotation using Spring JPA?

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.

How do you sort JPA?

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.

Which keyword is used for defining transaction in JPA configuration file?

To allow your query methods to be transactional simply use @Transactional at the repository interface you define.

How do I sort by criteria in JPA?

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.

What is the use of distinct in JPA?

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.

What is specification and predicate in JPA?

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.

What is JPA specification in spring?

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.


1 Answers

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.

like image 68
crizzis Avatar answered Oct 19 '22 18:10

crizzis