使用 Spring Boot 和 Kotlin 进行 Hibernate 急切加载整个对象图
Posted
技术标签:
【中文标题】使用 Spring Boot 和 Kotlin 进行 Hibernate 急切加载整个对象图【英文标题】:Hibernate with Spring Boot and Kotlin eager-loads the whole object graph 【发布时间】:2020-01-26 14:14:02 【问题描述】:我已将 spring.jpa.open-in-view
设置为 false
并且警告不再出现在日志中,所以这不是原因。
一旦执行find()
或findById()
方法,就会获取整个对象图,所以我认为这也不是由序列化引起的(实体无论如何都没有实现Serializable
接口)。
所有的对象关系都是多对一的,所有的对象映射都标注为惰性的。
我能够避免急切加载的唯一方法是获取单个字段而不是整个实体,但这既耗时又脆弱,因此我不想使用此解决方案。
应用程序使用 Hibernate 5.3.7 和 Spring Boot 2.1.0。我们拥有的较旧的 Java 应用程序没有这个问题;它使用 Hibernate 4.3.8,不使用 Spring Boot。
这是代码(从业务案例中精简以尝试查明问题)。
@Entity
@Table(name = "VT_Invoice")
class Invoice(
@Id
@Column(name = "InvoiceID")
val invoiceId: Long
)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "EntityCode#ClientCore", referencedColumnName = "EntityCode#ClientCore")
var client: Client? = null
@Entity
@Table(name = "VT_ClientCore")
class Client(
@Id
@Column(name = "EntityCode#ClientCore")
val id: String
)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "EntityCode#Branch", referencedColumnName = "EntityCode#Branch")
var branch: Branch? = null
@Entity
@Table(name = "VT_Branch")
class Branch(
@Id
@Column(name = "EntityCode#Branch")
val id: String
)
interface InvoiceRepository : JpaRepository<Invoice, Long>
@Service
class InvoiceRetrievalService(
@Autowired val invoiceRepository: InvoiceRepository
)
@PersistenceContext
private lateinit var entityManager: EntityManager
fun fetchInvoice(invoiceId: Long): Invoice
val hibernateInvoice = entityManager.find(Invoice::class.java, invoiceId)
val springInvoice = invoiceRepository.findById(invoiceId)
return hibernateInvoice
编辑
调试Hibernate代码后发现,急切加载是由DefaultLoadEventListener.proxyOrLoad()
里面的这段代码触发的:
if ( !persister.hasProxy() )
return load( event, persister, keyToLoad, options );
AbstractEntityPersister.hasProxy()
的实现在两个 Hibernate 版本之间有所不同。
休眠 4.3.8:
public boolean hasProxy()
return entityMetamodel.isLazy();
entityMetamodel.isLazy()
为真,因此跳过链接对象加载。
休眠 5.3.7:
public boolean hasProxy()
// skip proxy instantiation if entity is bytecode enhanced
return entityMetamodel.isLazy() && !entityMetamodel.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading();
entityMetamodel.isLazy()
为 false,因此加载了链接对象。对字节码增强的检查结果证明是无关紧要的。
这是我在调查中所能得到的。我无法确定为什么两个版本的 Hibernate 以不同的方式初始化 lazy
属性;它被写入了几个地方,但我设置的断点都没有被命中,所以我假设它是通过某处的反射初始化的。
【问题讨论】:
启用 Hibernate 日志记录,您可以看到惰性实体在什么时候被初始化。然后你就可以开始调试了。 @Kayaman:谢谢。我已经用调试过程中发现的内容更新了问题。 它可能是一个错误实现的 equals/hashcode/toString 正在加载您的所有属性。 【参考方案1】:事实证明,lazy
属性是在这段 Hibernate 代码中初始化的:
lazy = persistentClass.isLazy() && (
// TODO: this disables laziness even in non-pojo entity modes:
!persistentClass.hasPojoRepresentation() ||
!ReflectHelper.isFinalClass( persistentClass.getProxyInterface() )
);
isFinalClass()
检查产生的差异 - 在 Kotlin 中,类默认是最终的,但在 Java 中不是。制作实体类open
解决了这个问题。
在重新阅读我的问题时,我发现我忘记提及没有此问题的旧应用程序是用 Java 编写的。我会更新问题。对此感到抱歉。
【讨论】:
以上是关于使用 Spring Boot 和 Kotlin 进行 Hibernate 急切加载整个对象图的主要内容,如果未能解决你的问题,请参考以下文章
是否可以在 Spring Boot 中使用 @Transactional 和 kotlin 协程?
MapStruct 不使用 Kotlin 和 Spring Boot 自动装配,使用 Gradle 构建
如何在 Spring Boot 和 Spring WebFlux 中使用“功能 bean 定义 Kotlin DSL”?