I migrated an application from Spring Boot 2 to Spring Boot 3. It worked before, but now I have a problem. Given an entity with this attribute:
@JsonIgnore
@ElementCollection(fetch = EAGER)
@CollectionTable(name = "MEMBER_ROLES", joinColumns = @JoinColumn(name = "MEMBER_ID"))
@Column(name = "AUTHORITY")
@Enumerated(STRING)
private Set<MemberRoleType> roles;
When this query is executed:
@Query("""
SELECT m
FROM Member m
WHERE m.disabledOn IS NULL
AND m.roles = eu.company.backend.model.domain.enumeration.MemberRoleType.ROLE_CONSUMER
""")
Set<Member> findActiveMembers();
Then this exception occurs, that google doesn't know about:
2023-05-15 13:31:59,109 ERROR [http-nio-8888-exec-1] [] o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: java.lang.ClassCastException: class org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl cannot be cast to class org.hibernate.metamodel.mapping.BasicValuedMapping (org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl and org.hibernate.metamodel.mapping.BasicValuedMapping are in unnamed module of loader 'app')] with root cause
java.lang.ClassCastException: class org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl cannot be cast to class org.hibernate.metamodel.mapping.BasicValuedMapping (org.hibernate.metamodel.mapping.internal.PluralAttributeMappingImpl and org.hibernate.metamodel.mapping.BasicValuedMapping are in unnamed module of loader 'app')
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitEnumLiteral(BaseSqmToSqlAstConverter.java:6290)
at org.hibernate.query.sqm.tree.expression.SqmEnumLiteral.accept(SqmEnumLiteral.java:150)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitComparisonPredicate(BaseSqmToSqlAstConverter.java:6557)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitComparisonPredicate(BaseSqmToSqlAstConverter.java:416)
at org.hibernate.query.sqm.tree.predicate.SqmComparisonPredicate.accept(SqmComparisonPredicate.java:104)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitJunctionPredicate(BaseSqmToSqlAstConverter.java:6364)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitJunctionPredicate(BaseSqmToSqlAstConverter.java:416)
at org.hibernate.query.sqm.tree.predicate.SqmJunctionPredicate.accept(SqmJunctionPredicate.java:74)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitWhereClause(BaseSqmToSqlAstConverter.java:2276)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQuerySpec(BaseSqmToSqlAstConverter.java:1823)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQuerySpec(BaseSqmToSqlAstConverter.java:416)
at org.hibernate.query.sqm.tree.select.SqmQuerySpec.accept(SqmQuerySpec.java:122)
at org.hibernate.query.sqm.spi.BaseSemanticQueryWalker.visitQueryPart(BaseSemanticQueryWalker.java:213)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitQueryPart(BaseSqmToSqlAstConverter.java:1679)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitSelectStatement(BaseSqmToSqlAstConverter.java:1477)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitSelectStatement(BaseSqmToSqlAstConverter.java:416)
at org.hibernate.query.sqm.tree.select.SqmSelectStatement.accept(SqmSelectStatement.java:213)
at org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.translate(BaseSqmToSqlAstConverter.java:711)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.buildCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:380)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.withCacheableSqmInterpretation(ConcreteSqmSelectQueryPlan.java:300)
at org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.performList(ConcreteSqmSelectQueryPlan.java:276)
at org.hibernate.query.sqm.internal.QuerySqmImpl.doList(QuerySqmImpl.java:571)
at org.hibernate.query.spi.AbstractSelectionQuery.list(AbstractSelectionQuery.java:363)
at org.hibernate.query.sqm.internal.QuerySqmImpl.list(QuerySqmImpl.java:1073)
at org.hibernate.query.Query.getResultList(Query.java:94)
at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:127)
at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:90)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:148)
at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:136)
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.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:164)
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:390)
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:134)
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 org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218)
at jdk.proxy3/jdk.proxy3.$Proxy202.findActiveMembers(Unknown Source)
at eu.company.backend.service.LoyaltyService.recalculateLoyaltyPointsAndTiers(LoyaltyService.java:97)
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)
I already tried variations like this, but didn't help.
eu.company.backend.model.domain.enumeration.MemberRoleType.ROLE_CONSUMER MEMBER OF m.roles
What can be wrong?
The correct query would be:
SELECT m
FROM Member m
WHERE m.disabledOn IS NULL
AND ROLE_CONSUMER member of m.roles
But due to a bug in the query translator this does not work in the current release.
The workaround solution is:
SELECT m
FROM Member m
WHERE m.disabledOn IS NULL
AND 'ROLE_CONSUMER' member of m.roles
Issue is here: https://hibernate.atlassian.net/browse/HHH-16604
I already have a partial fix here: https://github.com/hibernate/hibernate-orm/pull/6573