6

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

我知道我可以显式设置获取模式,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();
4

1 回答 1

2

简短的回答

您不能以这种方式对其进行配置,但您可以实现必要的行为。

长答案

正如您在Hibernate 5.2 User Guide中所读到的,有几种方法可以应用获取策略:

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

本质上是一种 EAGER 的获取方式。要获取的数据是通过使用 SQL 外连接获得的。

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

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

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

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

没有 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));

当然,为每个查询调用反射是低效的。您可以从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())
            ));
于 2017-11-23T11:22:08.397 回答