Skip to content

Interface projection fails to instantiate data class with non-null constraints #1687

Closed
@w3-3w

Description

@w3-3w

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)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions