Can I use inline value class for mapping an Entity...
# spring
p
Can I use inline value class for mapping an Entity field?
j
For spring-jdbc I had to add a converter for the Id field, but I think other fields worked without the converter but not sure.
p
I planned to use it for an Id field. How should a converter look?
p
Turns out I need jakarta.persistence.AttributeConverter for that I guess
👍 1
thanks for the example
Still struggling with converters. AttributeConverter and
@Convert(converter = Converter::class)
does not solve the issue. My setup:
Copy code
@JvmInline value class LicensePlate private constructor(val value: String) {
    companion object {
        fun fromString(s: String): LicensePlate = LicensePlate(s.replace("-", ""))
    }
}
Copy code
@Converter
class LicensePlateConverter : AttributeConverter<LicensePlate?, String?> {
    override fun convertToDatabaseColumn(value: LicensePlate?): String? = value?.value

    override fun convertToEntityAttribute(value: String?): LicensePlate? = value?.let { LicensePlate.fromString(it) }
}
Copy code
interface VehicleRepository : JpaRepository<Vehicle, LicensePlate> {
    fun deleteByRegnumNotIn(regnums: List<LicensePlate>)
}
Copy code
@Entity(name = "vehicles")
class Vehicle(
    @Convert(converter = LicensePlateConverter::class)
    @Column(name = "_id", columnDefinition = "character varying(20)")
    @Id
    var regnum: LicensePlate,
 ...
)
org.springframework.orm.jpa.JpaSystemException: Could not convert 'xxx.personnel.LicensePlate' to 'java.lang.String' using 'org.hibernate.type.descriptor.java.StringJavaType' to wrap during a call to
Copy code
val vehicleList = vehicleRepo.findAllById(regnums)
j
p
Hm, so I define my Id in the PK class, and what about the repository? Should it be Repository<Vehicle, String>, or Repository<Vehicle, LicensePlate>?
Copy code
@JvmInline
value class LicensePlate private constructor(
    val value: String) {
    companion object {
        fun fromString(s: String): LicensePlate = LicensePlate(s.replace("-", ""))
    }
}
Copy code
data class VehiclePK(
    @Convert(converter = LicensePlateConverter::class)
    @Column(name = "_id", columnDefinition = "character varying(20)")
    val regnum: LicensePlate
) : Serializable
Copy code
@Entity(name = "vehicles")
@IdClass(VehiclePK::class)
class Vehicle(
    @Id
    var regnum: LicensePlate,
Copy code
interface VehicleRepository : JpaRepository<Vehicle, VehiclePK> {
    fun deleteByRegnumNotIn(regnums: List<LicensePlate>)
}
but still getting
Copy code
org.springframework.orm.jpa.JpaSystemException: Error attempting to apply AttributeConverter: class java.lang.String cannot be cast to class hu.icontest.personnel.LicensePlate (java.lang.String is in module java.base of loader 'bootstrap'; xxx.personnel.LicensePlate is in unnamed module of loader 'app')
during a call of
Copy code
val vehicleList = vehicleRepo.findAllById(regnums)
s
let me ask you from the other side: Do you need this to be a
value class
? JPA is written to primarily work with Java classes, so I wouldn't be surprised for issues to arise when you use strictly Kotlin specific things like inline classes. Also, if you're bringing in JPA and all its machinery, I'm pretty confident performance advantage of using inline class here is negligible. I mean, if you're doing this for fun, than go for it, but if that's some real problem you're trying to solve, I would just go for normal
data class
(or even type alias or plain String if you don't have any other logic around
LicensePlate
class and just use it as an alias)
p
From DDD point of view, it adds much, if it is not just a string, but wrapped in its own type.
s
Sure, I'm not saying it doesn't add anything. That was just a general advice, of course it's context-dependent. Sometimes you might want to use String, other times value class, and other typealiases. Now I've seen that you limit
LicensePlate
creation to custom factory function so value class might indeed be a good fit for it. Didn't notice that earlier. That being said, I still think you might be better off using normal data class for this scenario, to save yourself a headache of fighting with JPA not being compatible with inline classes. Also, looks like issue for this was raised on spring data commons Github, you might want to take a look at it: https://github.com/spring-projects/spring-data-commons/issues/2868
p
Well, understood. I should then use inline classes at another levels of the app, not directly on the Entities.