使用 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 与 Kotlin 上传文件

Kotlin和Spring Boot请求身体验证

如何在 Spring Boot 和 Spring WebFlux 中使用“功能 bean 定义 Kotlin DSL”?

使用 Spring Boot 和 Kotlin 进行 Hibernate 急切加载整个对象图