【发布时间】:2021-04-04 21:23:36
【问题描述】:
我有一个 BaseService 类,它具有用于一般创建、更新、删除和查找实体类的方法。
我正在尝试在此类中创建一个方法,该方法仅查询我作为参数提供给该方法的属性列表。
出于性能原因,这是必要的,因为有很多属性的实体一直在查询某些属性,但它们几乎从不需要(并且为空)。
我想出的内容如下,适用于正常情况。但是,有一个极端情况,我想在元素集合中获取属性。当我向列表提供作为元素集合的元素时,该方法将失败并出现以下错误:
org.hibernate.QueryException: 不是实体 [选择 从 my.package.customAttribute 生成Alias0.valueTypeSubset 为 生成的别名0]
我的问题是:
- 如何以通用方式使用标准 api 获取元素集合。 PS:我注意到路径通常是休眠类型
SingleAttributePath,而当元素集合的类型是PluralAttributePath。 - 我这样做是否完全错误?使用 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