use*_*059 5 java spring hibernate kotlin
问题摘要
我有一个在 Spring Boot 2/Hibernate 5 项目中使用的自定义类型。我正在尝试将此项目迁移到 Spring Boot 3/Hibernate 6,但我似乎无法让此自定义类型正常工作。此类型是 UUID 的包装,在从数据库读取/写入数据库时应将其视为 UUID。
现有代码
我的TypedId类允许根据它们关联的实体来区分不同的 UUID。这是一个相对简单的类:
data class TypedId<T>(val uuid: UUID = UUID.randomUUID()) : Serializable, Comparable<TypedId<T>> {
constructor(id: String) : this(UUID.fromString(id))
override fun compareTo(other: TypedId<T>): Int = this.uuid.compareTo(other.uuid)
override fun toString(): String = uuid.toString()
}
Run Code Online (Sandbox Code Playgroud)
这是在我的所有 JPA 实体扩展的基本 DatabaseRecord 抽象类中配置的:
@MappedSuperclass
@TypeDef(defaultForType = TypedId::class, typeClass = TypedIdJpaType::class)
abstract class DatabaseRecord<T> : Persistable<TypedId<T>> {
@Id var uid: TypedId<T> = TypedId()
@Transient private var innerIsNew: Boolean = true
override fun getId(): TypedId<T> = uid
override fun isNew(): Boolean = innerIsNew
private fun handleIsNew() {
innerIsNew = false
}
@PrePersist
open fun onPrePersist() {
handleIsNew()
}
@PostLoad
open fun onPostLoad() {
handleIsNew()
}
}
Run Code Online (Sandbox Code Playgroud)
上述代码的重要部分是@TypeDef. 它指向配置整个类型定义的 JPA Type 类。以下是该注释引入的相关代码:
class TypedIdJpaType :
AbstractSingleColumnStandardBasicType<TypedId<*>>(
PostgresUUIDSqlTypeDescriptor.INSTANCE, TypedIdDescriptor.INSTANCE) {
override fun getName(): String = TypedId::class.java.simpleName
override fun registerUnderJavaType(): Boolean = true
}
class TypedIdDescriptor : AbstractTypeDescriptor<TypedId<*>>(TypedId::class.java) {
companion object {
val INSTANCE = TypedIdDescriptor()
}
override fun fromString(string: String): TypedId<*> = TypedId<Any>(string)
override fun <X : Any> wrap(value: X?, options: WrapperOptions): TypedId<*>? =
value?.let { nonNullValue ->
when (nonNullValue) {
is ByteArray ->
TypedId(UUIDTypeDescriptor.ToBytesTransformer.INSTANCE.parse(nonNullValue))
is String ->
TypedId<Any>(UUIDTypeDescriptor.ToStringTransformer.INSTANCE.parse(nonNullValue))
is UUID -> TypedId<Any>(nonNullValue)
else -> throw unknownWrap(nonNullValue::class.java)
}
}
override fun <X : Any> unwrap(value: TypedId<*>, type: Class<X>, options: WrapperOptions): X =
UUIDTypeDescriptor.INSTANCE.unwrap(value.uuid, type, options)
}
Run Code Online (Sandbox Code Playgroud)
最后,这是我为涉及所有这些代码的非常基本的测试用例创建的示例实体:
interface CountryId
@Entity
@Table(name = "countries")
class Country(var name: String = "") : DatabaseRecord<CountryId>()
Run Code Online (Sandbox Code Playgroud)
核心问题
在 Hibernate 6 中,@TypeDef、TypeDescriptor等全部被删除。这意味着整个转换机制TypedId不再起作用。我一直在尝试寻找替代解决方案。
问题
我尝试过一个Converter. 我尝试过实施AbstractStandardBasicType. 我现在很失落。
我一直在阅读新的 Hibernate 6 用户指南,但我从那里收集到的任何信息都没有帮助。
额外细节
发布这个问题后,我意识到错误消息应该有用。当我尝试使用 SpringJpaRepository保存(也称为插入)上述实体时,会发生这种情况:
could not execute statement [ERROR: column "uid" is of type uuid but expression is of type bytea
Hint: You will need to rewrite or cast the expression.
Run Code Online (Sandbox Code Playgroud)
哇,这真是个兔子洞。因此,该小组有一些快速发现。Hibernate 似乎正在放弃AbstractSingleColumnStandardBasicType类型定义的方法。即使我正确实施了它,注册它也是一场噩梦。注册它的主要问题是 Hibernate 6 中仍然存在的每个选项似乎都被标记为弃用。基本上整个方法是死胡同。
该AttributeConverter方法似乎也不适用于像我的TypedId. 我永远无法让 Hibernate 识别它。
最后,我发现同时使用 Hibernate 6 提供的@JavaType和机制就是解决方案。@JdbcType
首先,我需要编写一个JavaType实现,这很容易,因为我的类型只是一个包装器UUID:
class TypedIdJavaType:
AbstractClassJavaType<TypedId<*>>(TypedId::class.java) {
override fun <X : Any?> unwrap(value: TypedId<*>?, type: Class<X>, options: WrapperOptions?): X =
UUIDJavaType.INSTANCE.unwrap(value?.uuid, type, options)
override fun <X : Any?> wrap(value: X, options: WrapperOptions?): TypedId<*> =
TypedId<Any>(UUIDJavaType.INSTANCE.wrap(value, options))
}
Run Code Online (Sandbox Code Playgroud)
然后,我JavaType通过我的财产上的注释引用我的新财产uid。但是,我还需要包括@JdbcType(UUIDJdbcType::class)。见下文:
@MappedSuperclass
@TypeDef(defaultForType = TypedId::class, typeClass = TypedIdJpaType::class)
abstract class DatabaseRecord<T> : Persistable<TypedId<T>> {
@Id
@JavaType(TypedIdJavaType::class)
@JdbcType(UUIDJdbcType::class)
var uid: TypedId<T> = TypedId()
@Transient private var innerIsNew: Boolean = true
override fun getId(): TypedId<T> = uid
override fun isNew(): Boolean = innerIsNew
private fun handleIsNew() {
innerIsNew = false
}
@PrePersist
open fun onPrePersist() {
handleIsNew()
}
@PostLoad
open fun onPostLoad() {
handleIsNew()
}
}
Run Code Online (Sandbox Code Playgroud)
需要两者的原因似乎是@JdbcType告诉 Hibernate 在第一次扫描实体时该字段应该是什么。一旦它通过 知道它应该是什么类型@JdbcType,它就会检查其他注释、Hibernate 的配置等,以找到将提供的类型转换为 JDBC 类型的可用机制。
就是这样。这是一个巨大的兔子洞,IMO 目前还没有很好的记录。
| 归档时间: |
|
| 查看次数: |
2709 次 |
| 最近记录: |