Open
Description
Given following example with 2 entities: project and user, each having a custom datatype as id. User has a reference to project using AggregatedReference<ProjectEntity, ProjectId>
. When Upgrading Spring Boot from 3.2.5 to 3.3.1, the application fails to create a user because the AggregatedReference
is not resolved to a simple type (creating a project works).
During debugging I see that in Spring Boot 3.3.1 AggregatedReference<ProjectEntity, ProjectId>
is only converted to a ProjectId
while in Spring Boot 3.2.5 the resulting ProjectId
is further converted to UUID
by MappingRelationalConverter::getPotentiallyConvertedSimpleWrite
- SQL schema:
CREATE TABLE project (
id UUID NOT NULL PRIMARY KEY,
name varchar NOT NULL
);
CREATE TABLE user (
id UUID NOT NULL PRIMARY KEY,
name varchar NOT NULL,
project_id UUID NOT NULL REFERENCES project(id)
);
- Entity classes:
@Table("project")
@Data
public class ProjectEntity implements Persistable<ProjectId> {
@Id
@NonNull
private final ProjectId id;
private final String name;
}
@Table("user")
@Data
public class UserEntity implements Persistable<UserId> {
@Id
@NonNull
private final UserId id;
private final String name;
@NonNull
@Column("project_id")
private final AggregateReference<ProjectEntity, ProjectId> project;
}
- Custom Id (
UserId
is identical):
@Value(staticConstructor = "from")
final public class ProjectId {
@NonNull
@Getter
private final UUID value;
@Override
public String toString() {
return this.getValue().toString();
}
}
- Converters (those for
UserId
are identical):
@WritingConverter
public class ProjectIdToUUIDConverter implements Converter<ProjectId, UUID> {
@Override
public UUID convert(ProjectId source) {
return source.getValue();
}
}
@ReadingConverter
public class UUIDToProjectIdConverter implements Converter<UUID, ProjectId> {
@Override
public ProjectId convert(UUID source) {
return ProjectId.from(source);
}
}
- Stack trace:
org.springframework.data.relational.core.conversion.DbActionExecutionException: Failed to execute InsertRoot{entity=com.example.demo.model.UserEntity@1b06dc57, idValueSource=PROVIDED}
at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:118)
at org.springframework.data.jdbc.core.AggregateChangeExecutor.lambda$executeSave$0(AggregateChangeExecutor.java:61)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.springframework.data.relational.core.conversion.SaveBatchingAggregateChange.forEachAction(SaveBatchingAggregateChange.java:74)
at org.springframework.data.jdbc.core.AggregateChangeExecutor.executeSave(AggregateChangeExecutor.java:61)
at org.springframework.data.jdbc.core.JdbcAggregateTemplate.performSave(JdbcAggregateTemplate.java:491)
at org.springframework.data.jdbc.core.JdbcAggregateTemplate.save(JdbcAggregateTemplate.java:168)
at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.save(SimpleJdbcRepository.java:68)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158)
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)
... 21 more
Caused by: org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [INSERT INTO "user" ("id", "name", "project_id") VALUES (?, ?, ?)]
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:112)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:107)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:116)
at org.springframework.jdbc.core.JdbcTemplate.translateException(JdbcTemplate.java:1548)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:677)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:970)
at org.springframework.jdbc.core.JdbcTemplate.update(JdbcTemplate.java:991)
at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update(NamedParameterJdbcTemplate.java:337)
at org.springframework.data.jdbc.core.convert.InsertStrategyFactory$DefaultInsertStrategy.execute(InsertStrategyFactory.java:96)
at org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.insert(DefaultDataAccessStrategy.java:110)
at org.springframework.data.jdbc.core.JdbcAggregateChangeExecutionContext.executeInsertRoot(JdbcAggregateChangeExecutionContext.java:83)
at org.springframework.data.jdbc.core.AggregateChangeExecutor.execute(AggregateChangeExecutor.java:85)
... 35 more
Caused by: org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of com.example.demo.model.ProjectId. Use setObject() with an explicit Types value to specify the type to use.
at org.postgresql.jdbc.PgPreparedStatement.setObject(PgPreparedStatement.java:1076)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.setObject(HikariProxyPreparedStatement.java)
at org.springframework.jdbc.core.StatementCreatorUtils.setValue(StatementCreatorUtils.java:453)
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValueInternal(StatementCreatorUtils.java:247)
at org.springframework.jdbc.core.StatementCreatorUtils.setParameterValue(StatementCreatorUtils.java:163)
at org.springframework.jdbc.core.PreparedStatementCreatorFactory$PreparedStatementCreatorImpl.setValues(PreparedStatementCreatorFactory.java:287)
at org.springframework.jdbc.core.PreparedStatementCreatorFactory$PreparedStatementCreatorImpl.createPreparedStatement(PreparedStatementCreatorFactory.java:245)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:656)
... 42 more