java - 带有 CONCAT_WS 函数的 JPA CriteriaBuilder 抛出 NullPointerException
问题描述
我有以下用于过滤订单的 CriteriaQuery。
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<OrderReducedDTO> cq = cb.createQuery(OrderReducedDTO.class);
Root<Order> root = cq.from(Order.class);
Join<Order, Customer> joinCustomer = root.join(Order_.customer);
Join<Order, Shipment> joinShipment = root.join(Order_.shipment);
Join<Shipment, Carrier> joinCarrier = joinShipment.join(Shipment_.carrier);
Join<Order, Payment> joinPayment = root.join(Order_.payment);
Join<Payment, PaymentMethod> joinPaymentMethod = joinPayment.join(Payment_.paymentMethod);
Join<Shipment, Country> joinCountry = joinShipment.join(Shipment_.country);
cq.select(cb.construct(
OrderReducedDTO.class,
root.get(Order_.id),
root.get(Order_.incrementId),
root.get(Order_.state),
root.get(Order_.couponCode),
root.get(Order_.totalDiscount),
root.get(Order_.total),
root.get(Order_.originChannel),
root.get(Order_.branchOffice),
joinCarrier.get(Carrier_.carrierCode),
cb.function("CONCAT_WS", String.class,
cb.literal(","),
joinShipment.get(Shipment_.streetName),
joinShipment.get(Shipment_.streetNumber),
joinShipment.get(Shipment_.city),
joinCountry.get(Country_.name),
joinShipment.get(Shipment_.zipCode)
),
joinPaymentMethod.get(PaymentMethod_.code),
joinPayment.get(Payment_.paymentDate),
root.get(Order_.createdAt),
root.get(Order_.updatedAt),
root.get(Order_.externalId),
joinCustomer.get(Customer_.fullName)
));
... filters and predicates...
给我带来麻烦并导致 NPE 被抛出的部分是这个
cb.function("CONCAT_WS", String.class,
cb.literal(","),
joinShipment.get(Shipment_.streetName),
joinShipment.get(Shipment_.streetNumber),
joinShipment.get(Shipment_.city),
joinCountry.get(Country_.name),
joinShipment.get(Shipment_.zipCode)
)
更具体地说,当我使用该CONCAT_WS
功能时。如果我使用CONCAT
,它可以工作。这是我得到的堆栈跟踪:
java.lang.NullPointerException: null
at org.hibernate.hql.internal.NameGenerator.generateColumnNames(NameGenerator.java:27)
at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.generateColumnNames(SessionFactoryHelper.java:434)
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeColumnNames(SelectClause.java:270)
at org.hibernate.hql.internal.ast.tree.SelectClause.finishInitialization(SelectClause.java:260)
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:255)
at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:1026)
...
这是我的 OrderReducedDTO
@Getter
public class OrderReducedDTO {
@JsonProperty("order_id")
private Integer orderId;
@JsonProperty("increment_id")
private String incrementId;
private OrderStates state;
@JsonProperty("coupon_code")
private String couponCode;
@JsonProperty("total_discount")
private BigDecimal totalDiscount;
private BigDecimal total;
@JsonProperty("origin_channel")
private String originChannel;
@JsonProperty("branch_office")
private String branchOffice;
@JsonProperty("shipping_method")
private String shippingMethod;
@JsonProperty("shipping_address")
private String shippingAddress;
@JsonProperty("payment_method")
private String paymentMethod;
@JsonProperty("payment_date")
private Timestamp paymentDate;
@JsonProperty("created_at")
private Timestamp createdAt;
@JsonProperty("updated_at")
private Timestamp updatedAt;
@JsonProperty("external_id")
private String externalId;
@JsonProperty("customer_full_name")
private String customerFullName;
@Setter
private List<OrderProductReducedDTO> products;
public OrderReducedDTO(Integer orderId,
String incrementId,
OrderStates state,
String couponCode,
BigDecimal totalDiscount,
BigDecimal total,
String originChannel,
String branchOffice,
String shippingMethod,
String shippingAddress,
String paymentMethod,
Object paymentDate,
Object createdAt,
Object updatedAt,
String externalId,
String customerFullName) {
this.orderId = orderId;
this.incrementId = incrementId;
this.state = state;
this.couponCode = couponCode;
this.totalDiscount = totalDiscount;
this.total = total;
this.originChannel = originChannel;
this.branchOffice = branchOffice;
this.shippingMethod = shippingMethod;
this.shippingAddress = shippingAddress;
this.paymentMethod = paymentMethod;
this.paymentDate = (Timestamp) paymentDate;
this.createdAt = (Timestamp) createdAt; //https://hibernate.atlassian.net/browse/HHH-4179
this.updatedAt = (Timestamp) updatedAt;
this.externalId = externalId;
this.customerFullName = customerFullName;
}
}
我主要想知道的是我function
是否正确使用了该方法。我假设我是因为CONCAT
工作。
解决方案
在 Hibernate 中经过数小时的调试后,我终于找到了问题的根源:
org/hibernate/hql/internal/ast/tree/ConstructorNode.java
private Type[] resolveConstructorArgumentTypes() throws SemanticException {
SelectExpression[] argumentExpressions = this.collectSelectExpressions();
if (argumentExpressions == null) {
return new Type[0];
} else {
Type[] types = new Type[argumentExpressions.length];
for(int x = 0; x < argumentExpressions.length; ++x) {
types[x] = argumentExpressions[x].getDataType();
}
return types;
}
}
argumentExpressions[x].getDataType()
正在返回null
。
我搜索了一下,发现这可能是由于 Hibernate 不知道给定 SQL 函数的实际返回类型(显然它只知道最常见的返回类型)。然后我按照这个答案并实现了一个自定义MetadataBuilderContributor
,如下所示:
public class SqlFunctionsMetadataBuilderContributor implements MetadataBuilderContributor {
@Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"concat_ws",
new StandardSQLFunction("concat_ws", StandardBasicTypes.STRING)
);
}
}
在我的 application.properties 上,我添加了:
spring.jpa.properties.hibernate.metadata_builder_contributor=ar.com.glamit.glamitoms.config.hibernate.SqlFunctionsMetadataBuilderContributor
重新启动应用程序后,argumentExpressions[x].getDataType()
现在返回 aStringType
并且NullPointerException
消失了。
推荐阅读
- python - 读取多个 csv 文件并将它们转换为系列对象的优雅方式
- swift - 更改视图下线条的颜色
- c# - 如何将数据从 .txt 文件导入 datagridview
- linux - 为什么 -rpath 不起作用但 "export LD_LIBRARY_PATH=..." 起作用?
- javascript - 如何防止视频阵列播放相同的视频两次?
- python - ValueError:为plotly.graph_objs.Layout类型的对象指定的属性无效:'autotypenumbers'
- python - 使用 Class 将每个参数设置为另一个参数的朋友
- powershell - 运行包含 <、' 和 " 的 Windows 10 Powershell 命令时出错
- java - 在 pom 文件中迭代 @item@
- python - 为现有的熊猫数据框从数组创建多级索引?