首页 > 解决方案 > 带有 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工作。

标签: javajpacriteriaquery

解决方案


在 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消失了。


推荐阅读