JPA:在JPQL中使用构造函数表达式在DTO中包装单个实体时,绕开N + 1

问题描述

首先,我知道(Prevent "n+1 selects" with JPA/Hibernate constructor expression?)处的类似问题,但我的情况有所不同,其他问题没有足够的答案。

我注意到当我使用 Hibernate 作为我的 JPA 提供程序进行 JPQL 查询时,我尝试将一个实体包装到 DTO 通过使用构造函数表达式, Hibernate 似乎仅选择实体的ID,此ID随后会导致N + 1查询获取其余属性

休眠版本: 5.4.21。最终版本

查询

select new com.example.distanceResult(p,distance(:center,p.location)) from Place p where dwithin(:center,p.location,:radiusMeters) = true

查询日志:

select place1_.id as col_0_0_,st_distance(?,place0_.location) as col_1_0_ from places place0_ inner join places place1_ on (place0_.id=place1_.id) where st_dwithin(?,place0_.location,?)=true
binding parameter [1] as [OTHER] - [POINT (10.90943 48.37102)]
binding parameter [2] as [OTHER] - [POINT (10.90943 48.37102)]
binding parameter [3] as [DOUBLE] - [2000.0]

select place0_.id as id1_0_0_,place0_.location as location2_0_0_,place0_.name as name3_0_0_ from places place0_ where place0_.id=?
binding parameter [1] as [BIGINT] - [3]

select place0_.id as id1_0_0_,place0_.name as name3_0_0_ from places place0_ where place0_.id=?
binding parameter [1] as [BIGINT] - [4]

如您所见,我正在尝试使用 hibernate-spatial 进行“位于地理位置周围的实体”类型的查询,但我认为这与问题无关。如何告诉休眠选择整个实体,然后将其包装到 DTO 中?我已经尝试进行获取自连接,但这没有帮助。

java类(使用Lombok)

@Value
public class distanceResult {
    Place place;
    Double distanceMeters;
}
@NoArgsConstructor
@Getter
@Setter
@ToString
@Entity
@Table(name = "PLACES")
public class Place extends BaseEntity<Long> {

    public Place(String name,Point location) {
        this.name = name;
        this.location = location;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private Point location;
}

BaseEntity spring-data-jpa 中的AbstractPersistable差不多,没有ID定义。

解决方法

好吧,我找到了一个解决方案,但是不确定这是否是最好的解决方法。我没有使用 JPQL 中的构造函数表达式,而是使用了(休眠专用的)ResultTransformer

代码:

public List<DistanceResult> findInDistance(Point center,double radiusMeters) {
    String jpql = "select p,distance(:center,p.location) from Place p where dwithin(:center,p.location,:radiusMeters) = true";

    return (List<DistanceResult>) entityManager.createQuery(jpql)
            .setParameter("center",center)
            .setParameter("radiusMeters",radiusMeters)
            .unwrap(Query.class)
            .setResultTransformer(
                    (ListResultTransformer)
                            (tuple,aliases) -> new DistanceResult(
                                    (Place) tuple[0],((Number) tuple[1]).doubleValue()
                            )
            ).getResultList();
}

注意: ListResultTransformer是Vlad Mihalcea的休眠类型库的一部分。 (另请参见:https://cloud.google.com/run/docs/issues#ah

查询日志:

select place0_.id as col_0_0_,st_distance(?,place0_.location) as col_1_0_,place0_.id as id1_0_,place0_.location as location2_0_,place0_.name as name3_0_ from places place0_ where st_dwithin(?,place0_.location,?)=true
binding parameter [1] as [OTHER] - [POINT (10.90943 48.37102)]
binding parameter [2] as [OTHER] - [POINT (10.90943 48.37102)]
binding parameter [3] as [DOUBLE] - [2000.0]