通过无知解决 LazyInitializationException
Posted
技术标签:
【中文标题】通过无知解决 LazyInitializationException【英文标题】:Solving LazyInitializationException via ignorance 【发布时间】:2015-12-02 02:55:51 【问题描述】:这里有无数的问题,如何通过 Eager fetching、保持事务打开、打开另一个事务、OpenEntityManagerInViewFilter
等等来解决“无法初始化代理”问题。
但是是否可以简单地告诉 Hibernate 忽略该问题并假装集合为空?就我而言,之前不获取它只是意味着我不在乎。
这实际上是以下 Y 的 XY 问题:
我正在上课
class Detail
@ManyToOne(optional=false) Master master;
...
class Master
@OneToMany(mappedBy="master") List<Detail> details;
...
并且想要处理两种请求:一种返回单个master
及其所有details
,另一种返回master
s 列表而没有details
。结果由 Gson 转换为 JSON。
我已经尝试过session.clear
和session.evict(master)
,但它们没有触及用于代替details
的代理。有效的是
master.setDetails(nullOrSomeCollection)
这感觉相当hacky。我更喜欢“无知”,因为它通常适用于不知道代理的哪些部分。
编写一个 Gson TypeAdapter
忽略 AbstractPersistentCollection
和 initialized=false
的实例可能是一种方法,但这将取决于 org.hibernate.collection.internal
,这肯定不是一件好事。在TypeAdapter
中捕获异常听起来也好不到哪里去。
一些答案后更新
我的目标不是“获取数据加载而不是异常”,而是“如何获取null例外” 我
Dragan raises 一个有效的观点,忘记获取并返回错误的数据将比异常更糟糕。但是有一个简单的方法:
仅对集合执行此操作 永远不要为他们使用null
返回 null
而不是空集合作为未获取数据的指示
这样,结果永远不会被错误解释。如果我忘记取东西,响应将包含无效的null
。
【问题讨论】:
你读过我的解决方案***.com/questions/6354265/… @NassimMOUALEK 是的,我做到了。它还解决了另一个问题,即“如何加载数据而不是异常”,但我什么是“如何获取 null 而不是异常”。 我认为它可能会帮助您改变策略,您应该阅读此***.com/a/32039605/3252285 “如果我忘记获取某些东西,响应将包含无效的 null。” 但是您将如何区分故意未获取和忘记获取的集合?两者都返回null
。
@DraganBozanovic 在服务器上根本不在乎,它只是不在乎:从它的 POV 来看,它是否获取了一个字段,它总是正确的,永远不会忘记。 +++ Java 客户端的对象看起来像我拒绝在主代码中使用的 DTO 并执行非空检查。这也应该在测试中完成。 +++ 当看到一个空值而不是一个集合时,一个 javascript 客户端会简单地崩溃。集成测试会显示这一点。
【参考方案1】:
您可以使用 Hibernate.isInitialized,它是 Hibernate 公共 API 的一部分。
所以,在TypeAdapter
中,您可以添加如下内容:
if ((value instanceof Collection) && !Hibernate.isInitialized(value))
result = new ArrayList();
但是,在我看来,您的方法通常不是可行的方法。
“在我的情况下,之前不获取它只是意味着我不在乎。”
或者这意味着您忘记获取它并且现在您返回了错误的数据(比收到异常更糟糕;服务的消费者认为集合是空的,但事实并非如此)。
我不想提出“更好”的解决方案(这不是问题的主题,每种方法都有自己的优势),而是我在大多数用例中解决此类问题的方式(它是通常采用的方式)正在使用 DTO:只需定义一个代表服务响应的 DTO,将其填充到事务上下文中(那里没有 LazyInitializationException
s)并将其提供给将其转换为服务响应的框架(json 、xml 等)。
【讨论】:
isInitialized
很好,但我必须将它应用于所有领域......或者只是注册一个TypeAdapterFactory<Collection>
!凉爽的! +++ 关于返回错误数据,我同意你的观点,但有一个补救措施(不完美:服务器返回无效答案,但客户端检测到问题)。当然,DTO 会起作用,但我认为它是 boilerplate(尽管在大型项目中可能是必要的邪恶)。
当然,不要对每个字段单独进行,这会破坏目的。注册一个自定义序列化程序。
我会在尝试时接受您的回答(可能是下周)。【参考方案2】:
您可以尝试以下解决方案。
创建一个名为LazyLoader
的接口
@FunctionalInterface // Java 8
public interface LazyLoader<T>
void load(T t);
在你的服务中
public class Service
List<Master> getWithDetails(LazyLoader<Master> loader)
// Code to get masterList from session
for(Master master:masterList)
loader.load(master);
然后像下面这样调用这个服务
Service.getWithDetails(new LazyLoader<Master>()
public void load(Master master)
for(Detail detail:master.getDetails())
detail.getId(); // This will load detail
);
在 Java 8 中,您可以使用 Lambda,因为它是单一抽象方法 (SAM)。
Service.getWithDetails((master) ->
for(Detail detail:master.getDetails())
detail.getId(); // This will load detail
);
您可以将上述解决方案与session.clear
和session.evict(master)
一起使用
【讨论】:
看起来您正在解决“如何获取详细信息”的问题,但我的问题是“如何在没有详细信息且没有代理抛出的情况下获取Master
” . session.clear()
和 session.evict(master)
似乎都无济于事(代理恶魔仍然在 details
中,当我问任何事情时都贪婪地抛出)。【参考方案3】:
我过去曾提出过一个类似的问题 (why dependent collection isn't evicted when parent entity is),它已经得出了一个答案,您可以尝试针对您的案例。
【讨论】:
我看不到如何应用它,但这只是我缺乏知识。我实际上相信Dragan's answer 解决了它,我只是还没有尝试过。同时,我相信我很快就会处理缓存问题,所以我正在为您的问题添加书签。 你说你“session.evict(master),但他们没有触及用于代替细节的代理”。我的问题是关于驱逐依赖集合(在您的情况下为“详细信息”)。 我想,驱逐是我的一个坏主意,但谢谢你,现在我明白了。【参考方案4】:解决方案是使用查询而不是关联(一对多或多对多)。甚至 Hibernate 的原作者之一也说Collections are a feature 而不是最终目标。
在您的情况下,您可以更灵活地删除集合映射,并在数据访问层中需要它们时简单地获取关联的关系。
【讨论】:
【参考方案5】:您可以为每个实体创建一个 Java 代理,这样每个方法都被一个 try/catch 块包围,当捕获到 LazyInitializationException
时返回 null
。
为此,您的所有实体都需要实现一个接口,并且您需要在整个程序中引用该接口(而不是实体类)。
如果您不能(或只是不想)使用接口,那么您可以尝试使用javassist 或cglib 构建动态代理,甚至手动构建,如this article 中所述。
如果您使用常见的 Java 代理,这里有一个草图:
public static <T> T ignoringLazyInitialization(
final Object entity,
final Class<T> entityInterface)
return (T) Proxy.newProxyInstance(
entityInterface.getClassLoader(),
new Class[] entityInterface ,
new InvocationHandler()
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args)
throws Throwable
try
return method.invoke(entity, args);
catch (InvocationTargetException e)
Throwable cause = e.getTargetException();
if (cause instanceof LazyInitializationException)
return null;
throw cause;
);
所以,如果你有一个实体A
如下:
public interface A
// getters & setters and other methods DEFINITIONS
及其实现:
public class AImpl implements A
// getters & setters and other methods IMPLEMENTATIONS
然后,假设您有对实体类的引用(由 Hibernate 返回),您可以按如下方式创建代理:
AImpl entityAImpl = ...; // some query, load, etc
A entityA = ignoringLazyInitialization(entityAImpl, A.class);
注意 1: 您还需要代理 Hibernate 返回的集合(留给读者作为练习);)
注意 2: 理想情况下,您应该在 DAO 或某种类型的外观中执行所有这些代理工作,以便所有内容对实体用户都是透明的
注意 3: 这绝不是最优的,因为它会为每次访问未初始化的字段创建一个堆栈跟踪
注意 4: 这可行,但增加了复杂性;考虑一下是否真的有必要。
【讨论】:
以上是关于通过无知解决 LazyInitializationException的主要内容,如果未能解决你的问题,请参考以下文章
我只是个无知的搬运工, 只能在这在这烦恼的夜晚做一点点搬运的工作