@Transactional(readOnly = true) 导致 LazyInitializationException

Posted

技术标签:

【中文标题】@Transactional(readOnly = true) 导致 LazyInitializationException【英文标题】:@Transactional(readOnly = true) leads to LazyInitializationException 【发布时间】:2012-10-09 12:55:46 【问题描述】:

我与链接表中的附加列存在多对多关系。我已经将它配置为拥有方吸引孩子们渴望(所以我没有得到LazyInitializationException),而在相反的方向它是懒惰的。这行得通。

我现在想微调事务(之前在 DAO 和服务类的类级别上只有 @Transactional。我将方法 getById 设置为 readOnly = true

@Transactional(readOnly  = true)
public Compound getById(Long id) 
    return compoundDAO.getById(id);

在此更改之后,我在以下 sn-p 中得到 LazyInitializationException

Compound compound = compoundService.getById(6L);        
Structure structure = compound.getComposition().get(0).getStructure();
System.out.println("StructureId: "+ structure.getId()); // LazyInitializationException

如果我删除 (readOnly = true) 这有效!谁能解释这种行为?我使用 Spring + Hibernate。有点令人困惑,因为我看不出这会影响加载哪些数据的任何原因?


编辑:

关系定义的片段。这是一个多对多,在链接表中有一列。

拥有方(例如复合包含结构):

@OneToMany(fetch = FetchType.EAGER, mappedBy = "pk.compound",
    cascade = CascadeType.ALL, orphanRemoval = true)
@OrderBy("pk.structure.id ASC")
private List<CompoundComposition> composition = new ArrayList<>();

属于一方:

@OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.structure",
cascade = CascadeType.ALL)
@OrderBy("pk.compound.id ASC")
private List<CompoundComposition> occurence;

@Embeddable ID 类中的多对一

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Compound getCompound() 
    return compound;


@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Structure getStructure() 
    return structure;

编辑 2:

堆栈跟踪

org.hibernate.LazyInitializationException: could not initialize proxy - no Session
    at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:165) ~[hibernate-core-4.1.7.Final.jar:4.1.7.Final]
    at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:272) ~[hibernate-core-4.1.7.Final.jar:4.1.7.Final]
    at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:185) ~[hibernate-core-4.1.7.Final.jar:4.1.7.Final]
    at org.bitbucket.myName.myApp.entity.Structure_$$_javassist_0.getId(Structure_$$_javassist_0.java) ~[classes/:na]
    at org.bitbucket.myName.myApp.App.main(App.java:31) ~[classes/:na]

编辑 3:

另见我的评论:

Log 与 readOnly 非常不同,它缺少加载关系的部分,例如。日志中缺少一些选择。

编辑 4:

所以我厌倦了基本的 DriverManagerDataSource 和没有连接池。问题完全一样。对我来说,这似乎是 Hibernate 中的一个问题。

【问题讨论】:

什么是类级别的事务定义? 你能发布模型类的sn-ps吗?而且,DAO 类的顶部还有@Transactional 注释吗? 尝试了不同的版本,从仅在 Service 类中使用 @Transactional 到在所有 DAO 类中使用它,效果相同。添加只读会导致 LazyInitializationException。 我不认为您的异常是事务模式的原因,当您调用引发延迟初始化异常的语句时,Hibernate 会话是否仍然打开? 堆栈跟踪也会有所帮助 【参考方案1】:

这只是哇。我开始理解为什么有些人讨厌 ORM ......感觉就像我经常不得不花费数小时来解决一个奇怪的问题,而解决方案是一组非常具体的注释 + 一些代码来解决所说的限制注释。

首先是为什么会发生这种情况(为什么用哪些注释来表示,而不是在逻辑意义上,这是这里的实际问题,因为使用常识是没有用的。只有反复试验才有帮助)。在拥有方面,在@OneToMany 我有 orphanRemoval = true (我发现这是一致性所必需的。有人会认为数据库约束应该处理这个......只是可能让你发疯的众多事情之一。)。似乎如果事务不是只读的,那么这个设置会导致一些数据被获取,即使它是惰性的,即这里:

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Structure getStructure() 
    return structure;

在只读事务中,不会发生这种提取。我猜是因为如果您无法更改任何内容,您也不必删除孤儿,因此只读 tx 中不需要此设置背后的逻辑所需的任何数据。

因此,显而易见的解决方案是更改为 FetchType.EAGER。错误的!如果这样做,您将无法使用 session.merge 更新拥有方(复合)。这将导致 ***Error。

实际上已经提到了真正的解决方案。只需保持配置不变,但在服务层中显式加载所需的关系:

@Transactional(readOnly = true)
@Override    
public Compound getById(Long id) 

    Compound  compound = compoundDAO.getById(id);
    for (CompoundComposition composition : compound.getComposition())
        Hibernate.initialize(composition.getStructure());
            
    return compound;

我承认我倾向于陷入过早的优化陷阱。这看起来效率不高,而且似乎首先破坏了 SQL 的工作方式。但是我很幸运,在大多数情况下 CompoundComposition 将只包含 1 或 2 个元素。

【讨论】:

你的前两段总结了我使用 Hibernate/JPA/ORM 的 6 年左右。【参考方案2】:

也许你可以把

value.getComposition().get(i).getStructure();

getById() 方法的主体中,以便延迟加载发生在事务中。我意识到在这种情况下,您必须遍历 i,这可能很不方便。

【讨论】:

【参考方案3】:

两件事:-

Lazy fetch 适用于 Collections 接口。由于...

@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public Structure getStructure() 
    return structure;

...这不是一个集合接口(就像List&lt;Structure&gt; 一样),它将以 Eager fetch 模式获取。

将服务方法设为事务性。似乎从 dao 层获取后,您的结构以 NEVER 刷新模式分离。我猜这是潜在的 ORM 问题。

【讨论】:

以上是关于@Transactional(readOnly = true) 导致 LazyInitializationException的主要内容,如果未能解决你的问题,请参考以下文章

Spring中@Transactional(readOnly = false)的作用是啥?

@Transactional (readOnly = false, propagation = Propagation.REQUIRED) 正在抛出异常

spring 事务管理之只读事务@Transactional(readOnly = true)

Spring 注解@Transactional readOnly=true

我可以在调用返回结果集但也更新行的存储过程的方法上使用@Transactional(readOnly = true) 吗?

只读事务@Transactional(readOnly = true)