Skip to content

Apply Kotlin Value Class unboxing for repository invocations #2868

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

Open
mp911de opened this issue Jun 28, 2023 · 5 comments
Open

Apply Kotlin Value Class unboxing for repository invocations #2868

mp911de opened this issue Jun 28, 2023 · 5 comments
Assignees
Labels
in: kotlin Kotlin support type: enhancement A general enhancement

Comments

@mp911de
Copy link
Member

mp911de commented Jun 28, 2023

When defining a value class as Id type ( MyRepository : Repository<MyEntity, MyValueClass>), calls that pass on Value class instances must apply wrapping/unwrapping according to the rules of how Kotlin expands Value class types.

It would be great to have an interceptor that translates arguments for findById(ID) calls, essentially all methods that accept ID.

See also spring-projects/spring-data-jpa#2840

@mp911de mp911de added type: enhancement A general enhancement in: kotlin Kotlin support labels Jun 28, 2023
@mp911de mp911de self-assigned this Jun 28, 2023
@w3-3w
Copy link

w3-3w commented Apr 27, 2024

This should also apply to audit types.

@JvmInline
value class Auditor(val str: String)

data class MyEntity(
    @Id val id: Long = 0L,
    val name: String,
    @CreatedBy val createdBy: Auditor = Auditor("UNKNOWN"),
)

@Configuration
@EnableR2dbcAuditing
class AuditConfig {
    @Bean
    fun auditorAware(): ReactiveAuditorAware<Auditor> = ReactiveAuditorAware {
        Auditor("test")
    }
}

In Spring Data 3.2.5, when setting audit parameters, exception throws like

java.lang.ClassCastException: class mypackage.Auditor cannot be cast to class java.lang.String (mypackage.Auditor is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')
	at mypackage.MyEntity_Accessor_uf7rrw.setProperty(Unknown Source)
	at org.springframework.data.mapping.model.ClassGeneratingPropertyAccessorFactory$KotlinValueBoxingAdapter.setProperty(ClassGeneratingPropertyAccessorFactory.java:1451)
	at org.springframework.data.mapping.model.InstantiationAwarePropertyAccessor.setProperty(InstantiationAwarePropertyAccessor.java:80)
	at org.springframework.data.mapping.model.SimplePersistentPropertyPathAccessor.setProperty(SimplePersistentPropertyPathAccessor.java:108)
	at org.springframework.data.mapping.model.SimplePersistentPropertyPathAccessor.setProperty(SimplePersistentPropertyPathAccessor.java:127)
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory$MappingMetadataAuditableBeanWrapper.lambda$setProperty$0(MappingAuditableBeanWrapperFactory.java:232)
	at java.base/java.lang.Iterable.forEach(Iterable.java:75)
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory$MappingMetadataAuditableBeanWrapper.setProperty(MappingAuditableBeanWrapperFactory.java:232)
	at org.springframework.data.auditing.MappingAuditableBeanWrapperFactory$MappingMetadataAuditableBeanWrapper.setLastModifiedBy(MappingAuditableBeanWrapperFactory.java:207)
	at org.springframework.data.auditing.AuditingHandlerSupport.touchAuditor(AuditingHandlerSupport.java:173)
	at org.springframework.data.auditing.AuditingHandlerSupport.lambda$touch$0(AuditingHandlerSupport.java:136)
	at java.base/java.util.Optional.map(Optional.java:260)
	at org.springframework.data.auditing.AuditingHandlerSupport.touch(AuditingHandlerSupport.java:134)
	at org.springframework.data.auditing.AuditingHandlerSupport.markModified(AuditingHandlerSupport.java:127)
	at org.springframework.data.auditing.ReactiveAuditingHandler.lambda$markModified$1(ReactiveAuditingHandler.java:90)
        ...

@mp911de
Copy link
Member Author

mp911de commented Apr 29, 2024

@w3-3w I created #2868 out of your comment. While your case doesn't relate to repository invocations per-se, it is still something that works differently between reflective and generated property accessors that we should address.

@larsw
Copy link

larsw commented Jun 12, 2024

I've had success with the following workaround for now:

@Repository
interface ContactRepository : JpaRepository<Contact, Contact.Ref> {
    @Query("select count(c) > 0 from Contact c where c._id = :id")
    override fun existsById(id: Contact.Ref): Boolean

    @Query("select c from Contact c where c._id = :id")
    override fun findById(id: Contact.Ref): Optional<Contact>
}

Where Contact.Ref is a Kotlin value class wrapping String.

@utikeev
Copy link

utikeev commented Sep 5, 2024

Another place for unboxing that should be considered is collections, because value classes are always wrapped when used as generic types.

E.g.:

value class UserId(val value: String)

data class Entity(
  @Id
  val from: UserId,
  val to: UserId,
)

interface EntityRepository : Repository<Entity, UserId> {
  fun findAllByToIn(to: List<UserId>): List<Entity>
}

@c-schicho
Copy link

we currently have the same issue. is there any solution work-around for this already?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: kotlin Kotlin support type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

5 participants