Closed
Description
TL;DR: When using combination of Kotlin and R2dbc, interface-based and class-based projection does not work as described in documentation.
Spring Data Version: 3.2.0
Example:
@Table("user")
@Immutable
data class UserEntity(
@Id
val id: Long = 0L,
val name: String,
val loginId: String,
)
interface UserProjection {
val id: Long
val name: String
}
data class UserDto(
val id: Long,
val name: String,
)
interface UserRepository : CoroutineCrudRepository<UserEntity, Long> {
suspend fun findByLoginId(loginId: String): List<UserDto> // (1) this causes startup failure
suspend fun getByLoginId(loginId: String): List<UserProjection> // (2) this throws runtime exception
}
For (1), the whole application fails to start with exception:
Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.lang.Object my.package.UserRepository.findByLoginId(java.lang.String,kotlin.coroutines.Continuation); Reason: Failed to create query for method public abstract java.lang.Object my.package.UserRepository.findByLoginId(java.lang.String,kotlin.coroutines.Continuation); No property 'loginId' found for type 'UserDto'
at org.springframework.data.repository.query.QueryCreationException.create(QueryCreationException.java:101)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:115)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.mapMethodsToQuery(QueryExecutorMethodInterceptor.java:99)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lambda$new$0(QueryExecutorMethodInterceptor.java:88)
at java.base/java.util.Optional.map(Optional.java:260)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.<init>(QueryExecutorMethodInterceptor.java:88)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:357)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:279)
at org.springframework.data.util.Lazy.getNullable(Lazy.java:135)
at org.springframework.data.util.Lazy.get(Lazy.java:113)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:285)
at org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactoryBean.afterPropertiesSet(R2dbcRepositoryFactoryBean.java:159)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1822)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1771)
... 30 common frames omitted
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.lang.Object my.package.UserRepository.findByLoginId(java.lang.String,kotlin.coroutines.Continuation); No property 'loginId' found for type 'UserDto'
at org.springframework.data.r2dbc.repository.query.PartTreeR2dbcQuery.<init>(PartTreeR2dbcQuery.java:74)
at org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory$R2dbcQueryLookupStrategy.resolveQuery(R2dbcRepositoryFactory.java:179)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:111)
... 42 common frames omitted
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property 'loginId' found for type 'UserDto'
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:90)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:443)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:419)
at org.springframework.data.mapping.PropertyPath.lambda$from$0(PropertyPath.java:372)
at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:354)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:332)
at org.springframework.data.repository.query.parser.Part.<init>(Part.java:81)
at org.springframework.data.repository.query.parser.PartTree$OrPart.lambda$new$0(PartTree.java:259)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.springframework.data.repository.query.parser.PartTree$OrPart.<init>(PartTree.java:260)
at org.springframework.data.repository.query.parser.PartTree$Predicate.lambda$new$0(PartTree.java:389)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197)
at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:992)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:682)
at org.springframework.data.repository.query.parser.PartTree$Predicate.<init>(PartTree.java:390)
at org.springframework.data.repository.query.parser.PartTree.<init>(PartTree.java:103)
at org.springframework.data.r2dbc.repository.query.PartTreeR2dbcQuery.<init>(PartTreeR2dbcQuery.java:70)
... 44 common frames omitted
For (2), exception is like:
Caused by: java.lang.NullPointerException: Parameter specified as non-null is null: method my.package.UserEntity.<init>, parameter loginId
at my.package.UserEntity.<init>(TestUserRepository.kt)
at my.package.UserEntity.<init>(TestUserRepository.kt:10)
at my.package.UserEntity_Instantiator_n2pr7c.newInstance(Unknown Source)
at org.springframework.data.mapping.model.KotlinClassGeneratingEntityInstantiator$DefaultingKotlinClassInstantiatorAdapter.createInstance(KotlinClassGeneratingEntityInstantiator.java:100)
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.relational.core.conversion.MappingRelationalConverter.read(MappingRelationalConverter.java:298)
at org.springframework.data.relational.core.conversion.MappingRelationalConverter.read(MappingRelationalConverter.java:294)
at org.springframework.data.r2dbc.core.R2dbcEntityTemplate.lambda$getRowsFetchSpec$13(R2dbcEntityTemplate.java:795)
at io.asyncer.r2dbc.mysql.MySqlResult.lambda$map$1(MySqlResult.java:87)