Skip to content

Regression on entities with Enum[] fields #1460

Closed
@nvervelle

Description

@nvervelle

With the upgrade from spring boot 3.0.4 (spring-data-bom:2022.0.3) to spring boot 3.0.5 (spring-data-bom:2022.0.4), entities with arrays of enumerated types are not working anymore. Our application relies a lot on such arrays, and it fails for each when upgrading to this minor release.

It seems to be related to this change.

User object :

public record User(String username, Locale locale, Color color, List<Color> colors) {}

Color enum :

public enum Color {
  GREEN,
  YELLOW,
  RED,
  BLUE
}

Entity object:

@Table("sample_user")
public record UserEntity(
    @Id @Column("user_id") FakeFunctionalUuid userId,
    @Column("username") String username,
    @Column("locale") Locale locale,
    @Column("color") Color color,
    @Column("colors") Color[] colors) {

  public static UserEntity from(final FakeFunctionalUuid userId, final User user) {
    return new UserEntity(
        userId, user.username(), user.locale(), user.color(), user.colors().toArray(Color[]::new));
  }

  public User toUser() {
    return new User(username, locale, color, Arrays.stream(colors).toList());
  }

  @Override
  public boolean equals(final Object o) {
    if (this == o) {
      return true;
    }
    if (!(o instanceof final UserEntity that)) {
      return false;
    }
    return userId.equals(that.userId)
        && username.equals(that.username)
        && locale.equals(that.locale)
        && color == that.color
        && Arrays.equals(colors, that.colors);
  }

  @Override
  public int hashCode() {
    return Objects.hash(userId);
  }

  @Override
  public String toString() {
    return new StringJoiner(", ", MethodHandles.lookup().lookupClass().getSimpleName() + "[", "]")
        .add("userId=" + userId)
        .add("username='" + username)
        .add("locale=" + locale)
        .add("color=" + color)
        .add("colors=" + Arrays.toString(colors))
        .toString();
  }
}

Example of stack trace when trying to save such an entity:

UserServiceTest > should save a user containing empty color list FAILED
    org.springframework.data.relational.core.conversion.DbActionExecutionException: Failed to execute InsertRoot{entity=UserEntity[userId=7b2817d1-ac50-4826-8dc5-92c25be72b03, username='John, locale=en_US, color=RED, colors=[]], idValueSource=PROVIDED}
        at app//org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:118)
        at app//org.springframework.data.jdbc.core.AggregateChangeExecutor.lambda$executeSave$0(AggregateChangeExecutor.java:61)
        at java.base@17.0.1/java.util.ArrayList.forEach(ArrayList.java:1511)
        at app//org.springframework.data.relational.core.conversion.SaveBatchingAggregateChange.forEachAction(SaveBatchingAggregateChange.java:74)
        at app//org.springframework.data.jdbc.core.AggregateChangeExecutor.executeSave(AggregateChangeExecutor.java:61)
        at app//org.springframework.data.jdbc.core.JdbcAggregateTemplate.performSave(JdbcAggregateTemplate.java:446)
        at app//org.springframework.data.jdbc.core.JdbcAggregateTemplate.insert(JdbcAggregateTemplate.java:195)
        at app//com.quicksign.kyc.starter.database.infra.repository.WithInsertDelegateImpl.insert(WithInsertDelegateImpl.java:18)
        at app//com.quicksign.kyc.starter.database.domain.repository.WithInsertImpl.insert(WithInsertImpl.java:18)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base@17.0.1/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base@17.0.1/java.lang.reflect.Method.invoke(Method.java:568)
        at app//org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at app//org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
        at app//org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
        at app//io.micrometer.observation.Observation.observeChecked(Observation.java:594)
        at app//com.quicksign.kyc.starter.observability.domain.ObservationService.observeChecked(ObservationService.java:24)
        at app//com.quicksign.kyc.starter.database.infra.observability.TransactionObservabilityAspect.measureTransactionTime(TransactionObservabilityAspect.java:70)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base@17.0.1/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base@17.0.1/java.lang.reflect.Method.invoke(Method.java:568)
        at app//org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:637)
        at app//org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:627)
        at app//org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)
        at app//org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
        at app//org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
        at app//org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)
        at app//org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
        at app//org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
        at app//org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702)
        at app//com.quicksign.kyc.starter.database.domain.repository.WithInsertImpl$$SpringCGLIB$$0.insert(<generated>)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base@17.0.1/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base@17.0.1/java.lang.reflect.Method.invoke(Method.java:568)
        at app//org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:288)
        at app//org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:136)
        at app//org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:120)
        at app//org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516)
        at app//org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
        at app//org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
        at app//org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
        at app//org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:391)
        at app//org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218)
        at app/jdk.proxy3/jdk.proxy3.$Proxy97.insert(Unknown Source)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base@17.0.1/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base@17.0.1/java.lang.reflect.Method.invoke(Method.java:568)
        at app//org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at app//org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:218)
        at app/jdk.proxy3/jdk.proxy3.$Proxy97.insert(Unknown Source)
        at app//com.quicksign.kyc.starter.database.infra.dao.UserRepositoryImpl.insert(UserRepositoryImpl.java:20)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
        at java.base@17.0.1/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base@17.0.1/java.lang.reflect.Method.invoke(Method.java:568)
        at app//org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
        at app//org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
        at app//org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
        at app//org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
        at app//org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
        at app//org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:702)
        at app//com.quicksign.kyc.starter.database.infra.dao.UserRepositoryImpl$$SpringCGLIB$$0.insert(<generated>)
        at app//com.quicksign.kyc.starter.database.domain.repository.UserService.create(UserService.java:20)
        at app//com.quicksign.kyc.starter.database.infra.repository.UserServiceTest.findByIdWithEmptyColorList(UserServiceTest.java:50)

        Caused by:
        org.springframework.jdbc.BadSqlGrammarException: ConnectionCallback; bad SQL grammar []
            at app//org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:99)
            at app//org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:70)
            at app//org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:79)
            at app//org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1538)
            at app//org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:342)
            at app//org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory.createArray(DefaultJdbcTypeFactory.java:74)
            at app//org.springframework.data.jdbc.core.convert.BasicJdbcConverter.writeJdbcValue(BasicJdbcConverter.java:277)
            at app//org.springframework.data.jdbc.core.convert.SqlParametersFactory.addConvertedValue(SqlParametersFactory.java:190)
            at app//org.springframework.data.jdbc.core.convert.SqlParametersFactory.addConvertedPropertyValue(SqlParametersFactory.java:177)
            at app//org.springframework.data.jdbc.core.convert.SqlParametersFactory.lambda$getParameterSource$2(SqlParametersFactory.java:260)
            at app//org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:298)
            at app//org.springframework.data.mapping.PersistentEntity.doWithAll(PersistentEntity.java:284)
            at app//org.springframework.data.jdbc.core.convert.SqlParametersFactory.getParameterSource(SqlParametersFactory.java:238)
            at app//org.springframework.data.jdbc.core.convert.SqlParametersFactory.forInsert(SqlParametersFactory.java:74)
            at app//org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.insert(DefaultDataAccessStrategy.java:104)
            at app//org.springframework.data.jdbc.core.JdbcAggregateChangeExecutionContext.executeInsertRoot(JdbcAggregateChangeExecutionContext.java:74)
            at app//org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:85)
            ... 90 more

            Caused by:
            org.postgresql.util.PSQLException: Unable to find server array type for provided name UNKNOWN.
                at app//org.postgresql.jdbc.PgConnection.createArrayOf(PgConnection.java:1458)
                at app//org.postgresql.jdbc.PgConnection.createArrayOf(PgConnection.java:1478)
                at app//com.zaxxer.hikari.pool.HikariProxyConnection.createArrayOf(HikariProxyConnection.java)
                at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                at java.base@17.0.1/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
                at java.base@17.0.1/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                at java.base@17.0.1/java.lang.reflect.Method.invoke(Method.java:568)
                at app//org.springframework.jdbc.core.JdbcTemplate$CloseSuppressingInvocationHandler.invoke(JdbcTemplate.java:1606)
                at app/jdk.proxy3/jdk.proxy3.$Proxy89.createArrayOf(Unknown Source)
                at app//org.springframework.data.jdbc.core.convert.DefaultJdbcTypeFactory.lambda$createArray$1(DefaultJdbcTypeFactory.java:74)
                at app//org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:334)
                ... 102 more

Metadata

Metadata

Assignees

No one assigned

    Labels

    type: regressionA regression from a previous release

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions