You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用CriteriaBuilder构建查询时Predicate抛出空指针异常问题排查

问题:使用CriteriaBuilder构建Hibernate查询时触发NullPointerException

我用CriteriaBuilder和Predicate构建Hibernate查询,代码如下:

public List<ProductOffer> getProductOffersByDateRange(Date startFromDate, Date startToDate, Date endFromDate, Date endToDate) {
    
    List<ProductOfferDAO> results = productOfferRepository.findAll(
            getSpecFromDatesAndExample(startFromDate, startToDate, endFromDate, endToDate, Example.of(new ProductOfferDAO())));
    
    return results.stream().map(productOfferTransformer::transform).collect(Collectors.toList());
}


private Specification<ProductOfferDAO> getSpecFromDatesAndExample(Date startFromDate,
                                                              Date startToDate,
                                                              Date endFromDate,
                                                              Date endToDate,
                                                              Example<ProductOfferDAO> example) {
return (Specification<ProductOfferDAO>) (root, query, builder) -> {
    final List<Predicate> predicates = new ArrayList<>();

    predicates.add(builder.and(builder.isNotNull(root.get("startDate")),
            builder.greaterThan(root.get("startDate").as(Date.class), startFromDate)));
    predicates.add(builder.and(builder.isNotNull(root.get("startDate")),
            builder.lessThan(root.get("startDate").as(Date.class), startToDate)));
    predicates.add(builder.and(builder.isNotNull(root.get("endDate")),
            builder.greaterThan(root.get("endDate").as(Date.class), endFromDate)));
    predicates.add(builder.and(builder.isNotNull(root.get("endDate")),
            builder.lessThan(root.get("endDate").as(Date.class), endToDate)));

    predicates.add(QueryByExamplePredicateBuilder.getPredicate(root, builder, example));

    return builder.and(predicates.toArray(new Predicate[predicates.size()]));
};
}

执行时出现如下错误堆栈:

{"timestamp":"2023-06-20T23:14:17.432Z","log_level":"ERROR","correlation_id":"rrt-098d5c0fd72b2251b-c-sy-18147-58271377-1","client_id":"135750084","request_context":{"method":"GET","path":"/productoffers","status-code":""},"response-time":"","trace_id":"1592056757147782155","dd":{"trace_id":"1592056757147782155","span_id":"8822821139017151941"},"message":"Error
java.lang.NullPointerException: Cannot invoke
\"org.hibernate.query.sqm.tree.predicate.SqmPredicate.copy(org.hibernate.query.sqm.tree.SqmCopyContext)\" because \"predicate\" is null
	at org.hibernate.query.sqm.tree.predicate.SqmJunctionPredicate.copy(SqmJunctionPredicate.java:54)
	at org.hibernate.query.sqm.tree.predicate.SqmJunctionPredicate.copy(SqmJunctionPredicate.java:21)
	at org.hibernate.query.sqm.tree.predicate.SqmWhereClause.copy(SqmWhereClause.java:33)
	at org.hibernate.query.sqm.tree.select.SqmQuerySpec.copy(SqmQuerySpec.java:104)
	at org.hibernate.query.sqm.tree.select.SqmQuerySpec.copy(SqmQuerySpec.java:53)
	at org.hibernate.query.sqm.tree.select.SqmSelectStatement.copy(SqmSelectStatement.java:122)
	at org.hibernate.query.sqm.tree.select.SqmSelectStatement.copy(SqmSelectStatement.java:42)
	at org.hibernate.query.sqm.internal.QuerySqmImpl.<init>(QuerySqmImpl.java:228)
	at org.hibernate.internal.AbstractSharedSessionContract.createCriteriaQuery(AbstractSharedSessionContract.java:1337)
	at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:1298)
	at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:126)
	at jdk.internal.reflect.GeneratedMethodAccessor96.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:360)
	at jdk.proxy3/jdk.proxy3.$Proxy201.createQuery(Unknown Source)
	at jdk.internal.reflect.GeneratedMethodAccessor96.invoke(Unknown Source)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:311)
	at jdk.proxy3/jdk.proxy3.$Proxy201.createQuery(Unknown Source)
	at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:748)
	at org.springframework.data.jpa.repository.support.SimpleJpaRepository.getQuery(SimpleJpaRepository.java:726)
	at org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll(SimpleJpaRepository.java:461)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:288)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136)
	at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120)
	at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)
	at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:77)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:163)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at datadog.trace.instrumentation.springdata.RepositoryInterceptor.invoke(RepositoryInterceptor.java:31)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
	at jdk.proxy3/jdk.proxy3.$Proxy221.findAll(Unknown Source)
	at au.com.mydump.offer.service.ProductOfferService.getProductOffersByDateRange(ProductOfferService.java:101)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:568)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)

问题原因

你创建的Example基于空的ProductOfferDAO对象(所有属性为null),QueryByExamplePredicateBuilder.getPredicate()在这种场景下会返回null。当把null添加到predicates列表后,调用builder.and()组合所有条件时,Hibernate处理空Predicate时触发了NullPointerException。

另外,日期查询逻辑存在冗余:同一个字段的多个条件可以合并,不需要重复执行isNotNull检查。

解决方案

1. 过滤空Predicate

在添加QueryByExample生成的Predicate前,先判断是否为null,避免将空值加入条件列表:

private Specification<ProductOfferDAO> getSpecFromDatesAndExample(Date startFromDate,
                                                              Date startToDate,
                                                              Date endFromDate,
                                                              Date endToDate,
                                                              Example<ProductOfferDAO> example) {
return (Specification<ProductOfferDAO>) (root, query, builder) -> {
    final List<Predicate> predicates = new ArrayList<>();

    // 合并startDate的条件,减少重复的isNotNull检查
    Predicate startDatePredicate = builder.and(
        builder.isNotNull(root.get("startDate")),
        builder.greaterThan(root.get("startDate").as(Date.class), startFromDate),
        builder.lessThan(root.get("startDate").as(Date.class), startToDate)
    );
    predicates.add(startDatePredicate);

    // 合并endDate的条件
    Predicate endDatePredicate = builder.and(
        builder.isNotNull(root.get("endDate")),
        builder.greaterThan(root.get("endDate").as(Date.class), endFromDate),
        builder.lessThan(root.get("endDate").as(Date.class), endToDate)
    );
    predicates.add(endDatePredicate);

    // 添加Example生成的Predicate,先检查是否非空
    Predicate examplePredicate = QueryByExamplePredicateBuilder.getPredicate(root, builder, example);
    if (examplePredicate != null) {
        predicates.add(examplePredicate);
    }

    return builder.and(predicates.toArray(new Predicate[predicates.size()]));
};
}

2. 移除无用的Example查询

如果当前场景不需要基于Example的查询(毕竟空对象的Example没有实际过滤作用),可以直接去掉这部分代码,简化逻辑:

修改getProductOffersByDateRange方法:

public List<ProductOffer> getProductOffersByDateRange(Date startFromDate, Date startToDate, Date endFromDate, Date endToDate) {
    
    List<ProductOfferDAO> results = productOfferRepository.findAll(
            getSpecFromDates(startFromDate, startToDate, endFromDate, endToDate));
    
    return results.stream().map(productOfferTransformer::transform).collect(Collectors.toList());
}

private Specification<ProductOfferDAO> getSpecFromDates(Date startFromDate,
                                                      Date startToDate,
                                                      Date endFromDate,
                                                      Date endToDate) {
return (Specification<ProductOfferDAO>) (root, query, builder) -> {
    final List<Predicate> predicates = new ArrayList<>();

    Predicate startDatePredicate = builder.and(
        builder.isNotNull(root.get("startDate")),
        builder.greaterThan(root.get("startDate").as(Date.class), startFromDate),
        builder.lessThan(root.get("startDate").as(Date.class), startToDate)
    );
    predicates.add(startDatePredicate);

    Predicate endDatePredicate = builder.and(
        builder.isNotNull(root.get("endDate")),
        builder.greaterThan(root.get("endDate").as(Date.class), endFromDate),
        builder.lessThan(root.get("endDate").as(Date.class), endToDate)
    );
    predicates.add(endDatePredicate);

    return builder.and(predicates.toArray(new Predicate[predicates.size()]));
};
}

3. 若需要Example查询,确保Example有有效条件

如果确实需要使用Example查询,要创建带有非空属性的ProductOfferDAO对象来构建Example,比如:

ProductOfferDAO exampleDAO = new ProductOfferDAO();
exampleDAO.setSomeField("targetValue"); // 设置需要过滤的属性
Example<ProductOfferDAO> example = Example.of(exampleDAO);

内容的提问来源于stack exchange,提问作者alex

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 16:56:59