使用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
相关产品推荐
相关产品推荐

