JPA Criteria使用带有连接而不是多个查询的SINGLE查询来查询急切获取关联实体

Yur*_*kov 6 java hibernate jpa jpa-criteria

我们正在从Hibernate本机标准转向JPA标准查询,将hibernate从4.3.11升级到5.2.12,并发现了不同的行为.以前的休眠标准使用带有连接的单个查询来急切获取一对多关联实体,但JPA使用单独的查询来获取每个根实体的关联实体.

我知道我可以显式设置fetch模式,entityRoot.fetch("attributes", JoinType.INNER);但是我们需要在一些AbstractDao实现中执行它,该实现应该适用于任何急切的一对多关联,因此无法明确设置它.

因此,我可以以某种方式告诉JPA标准,在默认情况下使用连接而不是针对每个根实体的单独查询,在单个查询中急切获取关联实体吗?

代码示例:

    CriteriaBuilder builder = createCriteriaBuilder();
    CriteriaQuery<T> criteriaQuery = builder.createQuery(getEntityClass());
    Root<T> entityRoot = criteriaQuery.from(getEntityClass());

    criteriaQuery.select(entityRoot);
    criteriaQuery.where(builder.equal(entityRoot.get("param1"), "value"));

    return getEntityManager().createQuery(criteriaQuery).getResultList();
Run Code Online (Sandbox Code Playgroud)

Ana*_*mov 3

简短回答

您无法以这种方式配置它,但您可以实现必要的行为。

长答案

正如您在Hibernate 5.2 用户指南中所读到的,有多种方法可以应用获取策略:

@Fetch注释是应用获取策略的静态FetchMode.JOIN方式,其工作原理与您所描述的完全一样:

本质上是一种急切的获取风格。要获取的数据是通过使用 SQL 外连接获得的。

问题是,即使您使用注释attributes标记集合@Fetch(FetchMode.JOIN),它也会被覆盖

我们不使用 JPQL 查询来获取多个部门实体的原因是因为 FetchMode.JOIN 策略将被查询获取指令覆盖。

要使用 JPQL 查询获取多个关系,必须使用 JOIN FETCH 指令。

因此,当通过标识符或自然 ID 直接获取实体时,FetchMode.JOIN 非常有用。

没有 JPA Criteria 查询FetchParent::fetch也会做同样的事情。

由于您需要抽象 DAO 的通用解决方案,因此可能的方法是通过反射处理所有渴望的一对多关联:

    Arrays.stream(getEntityClass().getDeclaredFields())
            .filter(field ->
                    field.isAnnotationPresent(OneToMany.class))
            .filter(field ->
                    FetchType.EAGER == field.getAnnotation(OneToMany.class).fetch())
            .forEach(field ->
                    entityRoot.fetch(field.getName(), JoinType.INNER));
Run Code Online (Sandbox Code Playgroud)

当然,为每个查询调用反射是低效的。您可以从Metamodel获取所有加载的@Entity类,处理它们并存储结果以供进一步使用:

    Metamodel metamodel = getEntityManager().getMetamodel();
    List<Class> entityClasses = metamodel.getEntities().stream()
            .map(Type::getJavaType)
            .collect(Collectors.toList());

    Map<Class, List<String>> fetchingAssociations = entityClasses.stream()
            .collect(Collectors.toMap(
                    Function.identity(),
                    aClass -> Arrays.stream(aClass.getDeclaredFields())
                            .filter(field -> 
                                    field.isAnnotationPresent(OneToMany.class))
                            .filter(field -> 
                                    FetchType.EAGER == field.getAnnotation(OneToMany.class).fetch())
                            .map(Field::getName)
                            .collect(Collectors.toList())
            ));
Run Code Online (Sandbox Code Playgroud)