Search code examples
javahibernatespring-data-jpaspring-data

Set<Enum> attribute in JPQL query returns exception at runtime


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?


Solution

  • 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
    

    UPDATE

    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