访问懒加载关联ID时触发加载的异常问题排查与解决
问题描述
我定义了两个相似的@ManyToOne懒加载关联:
@Entity class File { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "folder_id", nullable = true) var parent: Folder? = null @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) lateinit var user: User }
但关联的实体ID类型不同:
class Folder { @Id @Type(value = UniqueIdType::class) @Column(updatable = false, nullable = false) override lateinit var id: UniqueId } class User { @Id @Column(updatable = false, nullable = false) override var id: Long = 0 }
执行以下代码时,访问folder.id不会触发关联加载,但访问user.id会触发:
val file = fileRepo.findByIdOrNull(1) // 不会触发关联加载,ID可直接从外键获取 println(file.folder.id) // 会触发关联加载,原因是什么? println(file.user.id)
我怀疑问题出在user.id的基本类型上(Long会被转换为Java的long),该如何解决?
原因分析
核心问题确实和ID的类型有关:
Folder的ID是自定义引用类型UniqueId,Hibernate的懒加载代理可以直接从File实体存储的外键值中提取ID,无需加载Folder实体——因为引用类型允许代理在不初始化实体的情况下返回已缓存的外键值。User的ID是Kotlin的非空Long,编译后对应Java的long基本类型。由于Java基本类型不能为null,Hibernate的代理无法仅通过外键值直接构造一个有效的long返回(代理需要确保返回值符合非空基本类型的约束),因此会触发实体的懒加载来获取真实的ID值。
解决方案
有两种可靠的解决方式:
将User的ID改为可空类型
把User的ID声明为Long?,这样编译后对应Java的Long引用类型,代理可以直接从外键值返回ID,无需加载实体:class User { @Id @Column(updatable = false, nullable = false) override var id: Long? = null }注意:虽然数据库中
id不可空,但Kotlin的可空类型只是允许代理在未加载实体时返回null,实际从数据库查询到的实体ID永远不会为null。使用属性访问模式
在User的ID字段上添加@Access(AccessType.PROPERTY),让Hibernate通过getter方法获取ID,而非直接访问字段。同时确保getter方法返回Long(引用类型):class User { @Id @Column(updatable = false, nullable = false) @Access(AccessType.PROPERTY) private var _id: Long = 0 val id: Long get() = _id }这种方式既保留了ID的非空语义,又让代理可以通过getter返回已缓存的外键值,避免触发懒加载。
内容的提问来源于stack exchange,提问作者Hett
相关产品推荐
相关产品推荐

