【问题标题】:How to select multiple attributes from an entityClass using Criteria API in a generic way如何以通用方式使用 Criteria API 从 entityClass 中选择多个属性
【发布时间】:2021-04-04 21:23:36
【问题描述】:

我有一个 BaseService 类,它具有用于一般创建、更新、删除和查找实体类的方法。

我正在尝试在此类中创建一个方法,该方法仅查询我作为参数提供给该方法的属性列表。

出于性能原因,这是必要的,因为有很多属性的实体一直在查询某些属性,但它们几乎从不需要(并且为空)。

我想出的内容如下,适用于正常情况。但是,有一个极端情况,我想在元素集合中获取属性。当我向列表提供作为元素集合的元素时,该方法将失败并出现以下错误:

org.hibernate.QueryException: 不是实体 [选择 从 my.package.customAttribute 生成Alias0.valueTypeSubset 为 生成的别名0]

我的问题是:

  1. 如何以通用方式使用标准 api 获取元素集合。 PS:我注意到路径通常是休眠类型SingleAttributePath,而当元素集合的类型是PluralAttributePath
  2. 我这样做是否完全错误?使用 Criteria API 是否有更简单的方法?我可以想象用命名查询做这样的事情,但我只是放弃在实体类上使用命名查询,因为它变得不可读,我需要一种更灵活的方法。

我试图获取的实体类中的元素集合如下所示:

@ElementCollection(fetch = FetchType.EAGER)
@CollectionTable(name = "custom_attribute_value_type_subset", joinColumns = @JoinColumn(name = "custom_attribute_ref_key"))
@Column(columnDefinition = "text")
private Set<String> valueTypeSubset = new HashSet<>();

我将参数List.of("valueTypeSubSet") 提供给以下方法:

public List<T> findAllView(List<String> attributes) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<Tuple> cq = cb.createTupleQuery();
    Root<T> root = cq.from(clazz);
    List<Selection<?>> selections = attributes.stream()
        .map(attribute -> {
        Path<?> path = root.get(attribute);
        return path.alias(attribute);
    })
        .collect(Collectors.toList());
    cq.multiselect(selections);
    List<Tuple> tuples = em.createQuery(cq).getResultList();
    return tuples.stream().map(tuple -> populateEntity(selections, tuple)).collect(Collectors.toList());
}

private T populateEntity(List<Selection<?>> selections, Tuple tuple) {
    try {
        T object = clazz.getDeclaredConstructor().newInstance();
        selections.forEach(selection -> {
            String fieldName = selection.getAlias();
            Object value = tuple.get(fieldName);
            callSetter(object, fieldName, value);
        });
        return object;
    } catch (NoSuchMethodException | InstantiationException | IllegalAccessException
            | InvocationTargetException e) {
        log.error("Cannot create class instance.", e);
        return null;
    }
}

private void callSetter(Object obj, String fieldName, Object value) {
    PropertyDescriptor pd;
    try {
        pd = new PropertyDescriptor(fieldName, obj.getClass());
        pd.getWriteMethod().invoke(obj, value);
    } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
        log.error("Cannot call setter.", e);
    }
}

此示例使用以下方式运行:

  • Java 11
  • Quarkus 1.13
  • 休眠 5.4.29
  • Postgres 13.2

【问题讨论】:

  • This old issue 表示不支持。也许它仍然不受支持。你试过Selecting a wrapper吗?也许您可以使用相同的实体类并使用nullLiteral() 为您没有查询的字段传递空值。
  • 感谢您的想法。我可以尝试一个包装器,但是感觉就像很多代码重复和单独的类。此外,还有一些实体类的各种实现,这会使其不太适合。
  • 我尝试了包装器,但它在元素集合上也失败了。

标签: java hibernate criteria-api


【解决方案1】:

虽然这不是您问题的直接答案,但我认为这是 Blaze-Persistence Entity Views 的完美用例。

我创建了该库以允许在 JPA 模型和自定义接口或抽象类定义模型之间轻松映射,例如 Spring Data Projections on steroids。这个想法是您按照自己喜欢的方式定义目标结构(域模型),并通过 JPQL 表达式将属性(getter)映射到实体模型。

使用 Blaze-Persistence Entity-Views 的可能 DTO 模型如下所示:

@EntityView(User.class)
public interface UserDto {
    @IdMapping
    Long getId();
    String getName();
    Set<RoleDto> getRoles();

    @EntityView(Role.class)
    interface RoleDto {
        @IdMapping
        Long getId();
        String getName();
    }
}

查询是将实体视图应用于查询的问题,最简单的就是通过 id 进行查询。

UserDto a = entityViewManager.find(entityManager, UserDto.class, id);

最好的部分是,它只会获取实际需要的状态!有一个 Quarkus 集成,因此您甚至可以根据需要将其与本机图像一起使用。

【讨论】:

  • 看起来很有趣,感谢您的建议。但我想动态运行查询,而不是为每个视图创建一个 DTO。但我以后会更仔细地看看!
  • 您可以请求仅初始化您在实体视图中定义的属性的子集。这也用于在 GraphQL 集成中实现高效获取。我认为过去我也需要完全动态的模型,但事实证明,我大多只有 2-3 个表示形式(下拉、表格、细节)。对于表格表示,如果您支持动态列,则跳过属性是有意义的。
猜你喜欢
  • 2020-04-07
  • 1970-01-01
  • 2010-12-17
  • 2019-03-14
  • 2021-08-30
  • 1970-01-01
  • 1970-01-01
  • 2012-03-08
  • 2016-01-22
相关资源
最近更新 更多