迁移Hibernate 6后复用Collection类型ParameterExpression抛NoSuchElementException
Hibernate 6中CriteriaQuery复用Collection类型ParameterExpression触发NoSuchElementException的解决方案
迁移到Hibernate 6后,在CriteriaQuery里复用Collection类型的ParameterExpression时会抛出NoSuchElementException,旧版本Hibernate没有这个问题。推测是参数对应的Iterator被多次遍历导致。
示例代码:
public List<Long> example(Long userId) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Long> query = builder.createQuery(Long.class); Root<User> user = query.from(User.class); query.select(user.get(User_.id)); ParameterExpression<Collection<Long>> userIdParam = builder.parameter(generify(Collection.class)); query.where( builder.or( user.get(User_.id).in(userIdParam), user.get(User_.id).in(userIdParam) ) ); query.distinct(true); TypedQuery<Long> typedQuery = entityManager.createQuery(query); typedQuery.setParameter(userIdParam, Collections.singletonList(userId)); return typedQuery.getResultList(); }
不想为每个条件重复创建相同的参数表达式,求解决方案。
异常堆栈:
java.util.NoSuchElementException: null at java.util.Collections$1.next(Collections.java:4817) ~[?:?] at org.hibernate.query.sqm.internal.SqmUtil.createJdbcParameterBindings(SqmUtil.java:267) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final] at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.buildCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:358) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final] at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:268) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final] at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:244) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final] at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:518) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final] at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:367) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final] at org.hibernate.query.Query.getResultList(Query.java:119) ~[hibernate-core-6.2.5.Final.jar:6.2.5.Final] at jdk.internal.reflect.GeneratedMethodAccessor137.invoke(Unknown Source) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?] at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:405) ~[spring-orm-6.0.10.jar:6.0.10] at jdk.proxy3.$Proxy156.getResultList(Unknown Source) ~[?:?] at com.example.dao.UserDao.example(UserDao.java:265) ~[example-dao-3.22.1.jar:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?] at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:568) ~[?:?] at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:756) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-6.0.10.jar:6.0.10] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:756) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:756) ~[spring-aop-6.0.10.jar:6.0.10] at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708) ~[spring-aop-6.0.10.jar:6.0.10] at com.example.dao.UserDao$$SpringCGLIB$$0.example(<generated>) ~[example-dao-3.22.1.jar:?]
解决方案
1. 使用参数名称代替ParameterExpression
直接用字符串作为参数名,多次引用同一个名称,Hibernate只会处理一次参数绑定,避免Iterator被重复遍历:
public List<Long> example(Long userId) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Long> query = builder.createQuery(Long.class); Root<User> user = query.from(User.class); query.select(user.get(User_.id)); String userIdParamName = "userIdList"; query.where( builder.or( user.get(User_.id).in(builder.parameter(Collection.class, userIdParamName)), user.get(User_.id).in(builder.parameter(Collection.class, userIdParamName)) ) ); query.distinct(true); TypedQuery<Long> typedQuery = entityManager.createQuery(query); typedQuery.setParameter(userIdParamName, Collections.singletonList(userId)); return typedQuery.getResultList(); }
2. 提取重复Predicate复用
把重复的条件封装成一个Predicate,再在or逻辑里复用这个Predicate,这样ParameterExpression只会被解析一次:
public List<Long> example(Long userId) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<Long> query = builder.createQuery(Long.class); Root<User> user = query.from(User.class); query.select(user.get(User_.id)); ParameterExpression<Collection<Long>> userIdParam = builder.parameter(generify(Collection.class)); Predicate idInParam = user.get(User_.id).in(userIdParam); query.where( builder.or(idInParam, idInParam) ); query.distinct(true); TypedQuery<Long> typedQuery = entityManager.createQuery(query); typedQuery.setParameter(userIdParam, Collections.singletonList(userId)); return typedQuery.getResultList(); }
3. 升级Hibernate版本
这个问题大概率是Hibernate 6某个版本的bug,后续版本可能已经修复。可以尝试升级到最新的稳定版(比如6.3.x及以上),验证是否解决问题。
内容的提问来源于stack exchange,提问作者maszter
相关产品推荐
相关产品推荐

