如何按具有不同 JPA 规范的获取属性进行排序

How to sort by fetched properties with distinct JPA Specifications

我使用 Spring Boot 1.5.3.RELEASE,对我来说,不清楚如何使用 distinctSpecifications 按嵌套对象的属性排序,因为:

Caused by: org.postgresql.util.PSQLException: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list

Spring Data JPA 生成了错误的查询。

让我们看一个小例子:

型号

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

我们有 Vehicle class 嵌套对象 VehicleTypeVehicleBrand

@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 还包含 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;
}

服务

现在我想使用 JPA Specifications 创建一个查询并按 "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 数据 JPA 生成以下查询:

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

而且它完全不起作用,因为:

Order by expression "VEHICLEBRA1_.NAME" must be in the result list in this case; SQL statement

要解决这个问题,我们必须在 Specification:

中获取 vehicleBrand
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 数据 JPA 生成以下查询:

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

现在可以使用了,因为我们在 selection 部分看到了 vehiclebra1_.name

问题

但是如果我需要按 "vehicleBrand.model.name" 排序怎么办?
我做了一个额外的 fetch,但它不起作用:

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"));
}

它生成以下查询:

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

它不起作用是因为:

Order by expression "VEHICLEMOD4_.NAME" must be in the result list in this case; SQL statement

看看我们如何 select vehiclemod2_.name 但在 vehiclemod4_.name 之前下订单。

我尝试直接在 Specification 中进行排序,但它也不起作用:

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

我应该怎么做才能使 JPA 生成正确的查询,以便我可以按嵌套对象进行排序? 将 Spring Boot 的版本从 1.5.3.RELEASE 升级到 2+ 是否有意义?
谢谢。

这里有一个小秘密:您根本不需要使用 Sort 参数。

只需使用 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));

Sort 参数可能是在您的方案中添加额外连接的原因。