Skip to content

MappedCollection leads to ConverterNotFoundException when upgrading to 3.2 #1684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
blommish opened this issue Dec 4, 2023 · 11 comments
Closed
Assignees
Labels
type: regression A regression from a previous release

Comments

@blommish
Copy link

blommish commented Dec 4, 2023

Upgrading 3.1.4 -> 3.2.0
Using jdk temurin 17

When upgrading spring from 3.1.4 i get org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.util.UUID] to type [long]

Edit: Simplified test case: 56c4f62

Code: https://github.com/blommish/spring-data-error-3.2.0
Error: blommish/spring-data-error-3.2.0#1

Not really sure what the problem is, but it seems to be connected to that it is trying to map the value of the ExternalBarId (MappedCollection). The setup works fine not having a MappedCollection to another object.

image
Where the source is the barId (bar->id or bar_id)

data class Bar(
    @Id
    val id: UUID = UUID.randomUUID(),
    @MappedCollection(idColumn = "bar_id")
    val eksternId: ExternalBarId = ExternalBarId(),
)

@Table("bar_external")
data class ExternalBarId(
    @Id
    val id: Long = 0,
)
CREATE TABLE bar
(
    id UUID PRIMARY KEY
);

CREATE TABLE bar_external
(
    id     BIGSERIAL PRIMARY KEY,
    bar_id UUID NOT NULL REFERENCES bar (id) <---
);

Stacktrace

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.util.UUID] to type [long]
	at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:294)
	at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:185)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter.getPotentiallyConvertedSimpleRead(MappingRelationalConverter.java:652)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter$DefaultConversionContext.convert(MappingRelationalConverter.java:825)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter$ConversionContext.convert(MappingRelationalConverter.java:931)
	at org.springframework.data.jdbc.core.convert.MappingJdbcConverter$ResolvingConversionContext.convert(MappingJdbcConverter.java:451)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter$DocumentValueProvider.getPropertyValue(MappingRelationalConverter.java:1095)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter$DocumentValueProvider.getPropertyValue(MappingRelationalConverter.java:1054)
	at org.springframework.data.jdbc.core.convert.MappingJdbcConverter$ResolvingRelationalPropertyValueProvider.potentiallyAppendIdentifier(MappingJdbcConverter.java:342)
	at org.springframework.data.jdbc.core.convert.MappingJdbcConverter$ResolvingRelationalPropertyValueProvider.<init>(MappingJdbcConverter.java:329)
	at org.springframework.data.jdbc.core.convert.MappingJdbcConverter.newValueProvider(MappingJdbcConverter.java:299)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter$2.withContext(MappingRelationalConverter.java:499)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter$2.getPropertyValue(MappingRelationalConverter.java:487)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter$2.getPropertyValue(MappingRelationalConverter.java:475)
	at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:71)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter$ConvertingParameterValueProvider.getParameterValue(MappingRelationalConverter.java:1161)
	at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:49)
	at org.springframework.data.mapping.model.KotlinInstantiationDelegate.extractInvocationArguments(KotlinInstantiationDelegate.java:116)
	at org.springframework.data.mapping.model.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.createInstance(KotlinClassGeneratingEntityInstantiator.java:97)
	at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:98)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter.read(MappingRelationalConverter.java:454)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter.readAggregate(MappingRelationalConverter.java:348)
	at org.springframework.data.relational.core.conversion.MappingRelationalConverter.readAggregate(MappingRelationalConverter.java:311)
	at org.springframework.data.jdbc.core.convert.MappingJdbcConverter.readAndResolve(MappingJdbcConverter.java:287)
	at org.springframework.data.jdbc.core.convert.JdbcConverter.readAndResolve(JdbcConverter.java:106)
	at org.springframework.data.jdbc.core.convert.EntityRowMapper.mapRow(EntityRowMapper.java:82)
	at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94)
	at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:61)
	at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:733)
	at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:658)
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:723)
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:748)
	at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:804)
	at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.queryForObject(NamedParameterJdbcTemplate.java:252)
	at org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.findById(DefaultDataAccessStrategy.java:268)
	at org.springframework.data.jdbc.core.JdbcAggregateTemplate.findById(JdbcAggregateTemplate.java:290)
	at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.findById(SimpleJdbcRepository.java:79)
	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)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:352)
	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)
	at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168)
	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.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385)
	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.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:95)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:249)
	at jdk.proxy3/jdk.proxy3.$Proxy102.findById(Unknown Source)
	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)
	at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:352)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:249)
	...
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Dec 4, 2023
@blommish
Copy link
Author

blommish commented Dec 4, 2023

Having different names of the id-columns works, but the same does not (such as the reported issue)
blommish/spring-data-error-3.2.0#2

@blommish
Copy link
Author

blommish commented Dec 4, 2023

Using normal spring-data-jdbc (without using default UUID and InsertUpdateRepository) generates the same error.

@Repository
interface FooRepository : CrudRepository<Foo, UUID>

data class Foo(
    @Id
    val id: UUID? = null,
    @MappedCollection(idColumn = "foo_id")
    val eksternId: ExternalFooId = ExternalFooId(),
)

@Table("foo_external")
data class ExternalFooId(
    @Id
    val id: Long = 0,
)
CREATE TABLE foo
(
    id UUID DEFAULT uuid_generate_v4() PRIMARY KEY
);

CREATE TABLE foo_external
(
    id     BIGSERIAL PRIMARY KEY,
    foo_id UUID NOT NULL REFERENCES foo (id)
);

schauder added a commit that referenced this issue Dec 4, 2023
@schauder schauder self-assigned this Dec 4, 2023
@mp911de mp911de added type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged labels Dec 4, 2023
@blommish
Copy link
Author

blommish commented Dec 6, 2023

Simplified test case added, in a fork of spring-data-relational: 56c4f62

@schauder
Copy link
Contributor

schauder commented Dec 6, 2023

Preliminary analysis: For some reason we access the value for Foo.id in order to fill ExternalFooId.id.
And this happens only when the latter is annotated with @Id. So a quick workaround would be to remove that annotation which shouldn't have any effect.

@blommish
Copy link
Author

blommish commented Dec 6, 2023

Preliminary analysis: For some reason we access the value for Foo.id in order to fill ExternalFooId.id. And this happens only when the latter is annotated with @Id. So a quick workaround would be to remove that annotation which shouldn't have any effect.

Thanks for taking a look at it.
Removing the @Id from MappedCollection->Id insert 0 as a value into the database, instead of using the serial.
blommish@07b85ef

Not setting the id to 0 throws an exception of trying to insert null into the database.

The thing i was wondering was if I actually was using the MappedCollection wrong in this way I'm using it. Is it expected to be able to have a @Id inside the MappedCollection?
I could split these entities.
This is kind of a workaround of having an external serial id on an entity, but we didn't manage to get it to work having id and serial in the same entity.

create table foo (
id UUID PRIMARY KEY,
external_id serial
...
)

create unique index on foo (external_id) 

@schauder
Copy link
Contributor

schauder commented Dec 7, 2023

There is no such thing as an EmbeddedCollection in Spring Data JDBC.
You can have a @id in a referenced collection or one-to-one relationship, but in most cases you don't need it.
The exception is a Set of entities that themselves contain a 1:1 or 1:N relationship. Those Set elements need an id.

@blommish
Copy link
Author

blommish commented Dec 7, 2023

MappedCollection

Thanks! I meant MappedCollection

@charliemidtlyng
Copy link

charliemidtlyng commented Jan 11, 2024

There is no such thing as an EmbeddedCollection in Spring Data JDBC. You can have a @id in a referenced collection or one-to-one relationship, but in most cases you don't need it. The exception is a Set of entities that themselves contain a 1:1 or 1:N relationship. Those Set elements need an id.

I have the same issue, even with Set:

data class Bar(
    @Id
    val id: UUID = UUID.randomUUID(),
    @MappedCollection(idColumn = "bar_id")
    val eksternId: Set<ExternalBarId> = setOf(ExternalBarId()),
)

@Table("bar_external")
data class ExternalBarId(
    @Id
    val id: Long = 0,
)

When fetching a Bar this exception is thrown: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [java.util.UUID] to type [long]

Is this something you are looking into, @schauder?

Edit: Link to example code: https://github.com/blommish/spring-data-error-3.2.0/blob/spring-3.2.0-error-with-set/src/main/kotlin/no/blommish/BarRepository.kt

@charliemidtlyng
Copy link

Update - After some more testing:
It seems that it cannot handle two different Id-types for the main object and the MappedCollection-object.
This works:

data class Bar(
    @Id
    val id: String = UUID.randomUUID().toString(),
    @MappedCollection(idColumn = "bar_id")
    val eksternId: Set<ExternalBarId> = setOf(ExternalBarId("eksternId_${Random.nextInt(0, 10000)}")),
)

@Table("bar_external")
data class ExternalBarId(
    @Id
    val id: String,
)

This does NOT work:


data class Bar(
    @Id
    val id: UUID = UUID.randomUUID(),
    @MappedCollection(idColumn = "bar_id")
    val eksternId: Set<ExternalBarId> = setOf(ExternalBarId("eksternId_${Random.nextInt(0, 10000)}")),
)

@Table("bar_external")
data class ExternalBarId(
    @Id
    val id: String,
)

@sergey-morenets
Copy link

The same issue happened to me after upgrading to Spring Data JDBC 3.2.1.
Any ETA for fix?

charliemidtlyng added a commit to navikt/familie-ef-sak that referenced this issue Jan 22, 2024
schauder added a commit that referenced this issue Apr 19, 2024
When the child of a one-to-one relationship has an id, the value for that id gets read in the wrong way.
We get the column name for that id use that to access the value in the RowDocument.

This results in either no value at all being found or even worse, the value of a root entity with a property of same name being accessed.

This is fixed by using the full AggregatePath instead of just the property for accessing that value.

Closes #1684
schauder added a commit that referenced this issue Apr 19, 2024
When the child of a one-to-one relationship has an id, the value for that id gets read in the wrong way.
We get the column name for that id use that to access the value in the RowDocument.

This results in either no value at all being found or even worse, the value of a root entity with a property of same name being accessed.

This is fixed by using the full AggregatePath instead of just the property for accessing that value.

Closes #1684
schauder added a commit that referenced this issue Apr 19, 2024
When the child of a one-to-one relationship has an id, the value for that id gets read in the wrong way.
We get the column name for that id use that to access the value in the RowDocument.

This results in either no value at all being found or even worse, the value of a root entity with a property of same name being accessed.

This is fixed by using the full AggregatePath instead of just the property for accessing that value.

Closes #1684
@mp911de mp911de added type: regression A regression from a previous release and removed type: bug A general bug labels Apr 22, 2024
@mp911de mp911de added this to the 3.2.6 (2023.1.6) milestone Apr 22, 2024
@mp911de mp911de changed the title Spring data jdbc MappedCollection -> ConverterNotFoundException upgrading to 3.2.0 MappedCollection leads to ConverterNotFoundException when upgrading to 3.2 Apr 22, 2024
mp911de pushed a commit that referenced this issue Apr 22, 2024
When the child of a one-to-one relationship has an id, the value for that id gets read in the wrong way.
We get the column name for that id use that to access the value in the RowDocument.

This results in either no value at all being found or even worse, the value of a root entity with a property of same name being accessed.

This is fixed by using the full AggregatePath instead of just the property for accessing that value.

Closes #1684
Original pull request: #1775
@blommish
Copy link
Author

Thank you @schauder @mp911de !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: regression A regression from a previous release
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants