spring-projects / spring-data-relational

Spring Data Relational. Home of Spring Data JDBC and Spring Data R2DBC.
https://spring.io/projects/spring-data-jdbc
Apache License 2.0
753 stars 345 forks source link

Relation 1:N assumes foreign key is numeric during read conversion #1727

Closed razubuddy closed 2 months ago

razubuddy commented 7 months ago

I have simple 1:N relation

record Product(@Id String id, String name, Set<Part> parts, @Version Integer version) {}
record Part(@Id Integer id, String name) {}
interface ProductRepository extends CrudRepository<Product, String> {}

Postgres schema looks like:

create table if not exists product (
    id character varying(32) not null primary key,
    name character varying(255) not null,
    version int not null
);

create table if not exists part (
    id integer not null primary key,
    product character varying(32) not null references product(id),
    name character varying(255) not null
);

Test code:

var p1 = repository.save(new Product("P1", "product 1", Set.of(new Part(1, "part 1")), null));
var p2 = repository.save(new Product("P2", "product 2", Set.of(new Part(2, "part 2"), new Part(3, "part 3")), null));
System.out.println(p1);
System.out.println(p2);
System.out.println(repository.findAll());

Writing part works, but while reading from database it assumes foreign key product should be numeric:

org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.lang.Integer] for value [P1]
        at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:182) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter.getPotentiallyConvertedSimpleRead(MappingRelationalConverter.java:665) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter$DefaultConversionContext.convert(MappingRelationalConverter.java:837) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter$ConversionContext.convert(MappingRelationalConverter.java:943) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.core.convert.MappingJdbcConverter$ResolvingConversionContext.convert(MappingJdbcConverter.java:451) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter$DocumentValueProvider.getPropertyValue(MappingRelationalConverter.java:1107) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter$DocumentValueProvider.getPropertyValue(MappingRelationalConverter.java:1066) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.core.convert.MappingJdbcConverter$ResolvingRelationalPropertyValueProvider.potentiallyAppendIdentifier(MappingJdbcConverter.java:342) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.core.convert.MappingJdbcConverter$ResolvingRelationalPropertyValueProvider.<init>(MappingJdbcConverter.java:329) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.core.convert.MappingJdbcConverter.newValueProvider(MappingJdbcConverter.java:299) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter$2.withContext(MappingRelationalConverter.java:498) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter$2.getPropertyValue(MappingRelationalConverter.java:486) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter$2.getPropertyValue(MappingRelationalConverter.java:474) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:71) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter$ConvertingParameterValueProvider.getParameterValue(MappingRelationalConverter.java:1173) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:49) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:301) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:273) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:98) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter.read(MappingRelationalConverter.java:453) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter.readAggregate(MappingRelationalConverter.java:348) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.relational.core.conversion.MappingRelationalConverter.readAggregate(MappingRelationalConverter.java:311) ~[spring-data-relational-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.core.convert.MappingJdbcConverter.readAndResolve(MappingJdbcConverter.java:287) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.core.convert.JdbcConverter.readAndResolve(JdbcConverter.java:106) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.core.convert.EntityRowMapper.mapRow(EntityRowMapper.java:82) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:94) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:61) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:733) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:658) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:723) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:748) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:804) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.query(NamedParameterJdbcTemplate.java:218) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.query(NamedParameterJdbcTemplate.java:230) ~[spring-jdbc-6.1.3.jar:6.1.3]
        at org.springframework.data.jdbc.core.convert.DefaultDataAccessStrategy.findAll(DefaultDataAccessStrategy.java:276) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.core.JdbcAggregateTemplate.findAll(JdbcAggregateTemplate.java:341) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at org.springframework.data.jdbc.repository.support.SimpleJdbcRepository.findAll(SimpleJdbcRepository.java:89) ~[spring-data-jdbc-3.2.2.jar:3.2.2]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
        at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:351) ~[spring-aop-6.1.3.jar:6.1.3]
        at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new$0(RepositoryMethodInvoker.java:277) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:170) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:158) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:516) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:628) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
        at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:168) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:143) ~[spring-data-commons-3.2.2.jar:3.2.2]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
        at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) ~[spring-tx-6.1.3.jar:6.1.3]
        at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385) ~[spring-tx-6.1.3.jar:6.1.3]
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) ~[spring-tx-6.1.3.jar:6.1.3]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
        at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137) ~[spring-tx-6.1.3.jar:6.1.3]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
        at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97) ~[spring-aop-6.1.3.jar:6.1.3]
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.1.3.jar:6.1.3]
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:220) ~[spring-aop-6.1.3.jar:6.1.3]
        at com.example.datajdbc.$Proxy61.findAll(Unknown Source) ~[na:na]
        at com.example.datajdbc.DatajdbcApplication.lambda$applicationRunner$0(DatajdbcApplication.java:23) ~[main/:na]
        at org.springframework.boot.SpringApplication.lambda$callRunner$4(SpringApplication.java:786) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.util.function.ThrowingConsumer$1.acceptWithException(ThrowingConsumer.java:83) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.util.function.ThrowingConsumer.accept(ThrowingConsumer.java:60) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.util.function.ThrowingConsumer$1.accept(ThrowingConsumer.java:88) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:798) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:786) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.lambda$callRunners$3(SpringApplication.java:774) ~[spring-boot-3.2.2.jar:3.2.2]
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:na]
        at java.base/java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:357) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:510) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
        at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:na]
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:na]
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
        at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
        at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:774) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:341) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.2.jar:3.2.2]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.2.jar:3.2.2]
        at com.example.datajdbc.DatajdbcApplication.main(DatajdbcApplication.java:28) ~[main/:na]
Caused by: java.lang.NumberFormatException: For input string: "P1"
        at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67) ~[na:na]
        at java.base/java.lang.Integer.parseInt(Integer.java:662) ~[na:na]
        at java.base/java.lang.Integer.valueOf(Integer.java:989) ~[na:na]
        at org.springframework.util.NumberUtils.parseNumber(NumberUtils.java:203) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.core.convert.support.StringToNumberConverterFactory$StringToNumber.convert(StringToNumberConverterFactory.java:64) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.core.convert.support.StringToNumberConverterFactory$StringToNumber.convert(StringToNumberConverterFactory.java:50) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.core.convert.support.GenericConversionService$ConverterFactoryAdapter.convert(GenericConversionService.java:409) ~[spring-core-6.1.3.jar:6.1.3]
        at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41) ~[spring-core-6.1.3.jar:6.1.3]
razubuddy commented 7 months ago

I attach simple project to reproduce datajdbc.tar.gz

charliemidtlyng commented 7 months ago

Similar to this, i think: https://github.com/spring-projects/spring-data-relational/issues/1684

schauder commented 3 months ago

Please check if this issue is still present in the lates 3.2.x or 3.3.x releases. It looks very much like a duplicate of #1684 as @charliemidtlyng pointed out.

charliemidtlyng commented 3 months ago

I think there's still an issue with relations; A -> B[] -> C -> D[]

C.D[] is always empty on fetch. I'll try to make an integration test the next days.

charliemidtlyng commented 3 months ago

I think there's still an issue with relations; A -> B[] -> C -> D[]

C.D[] is always empty on fetch. I'll try to make an integration test the next days.

After local testing it looks like https://github.com/spring-projects/spring-data-relational/pull/1810 fixes my issue mentioned above.

I'm pretty sure 3.3.x and 3.2.6 fixes this issue reported by @razubuddy.

schauder commented 2 months ago

Thanks for the feedback. I'm closing this as a duplicate of #1802 fixed by #1810