通过忽略来解决LazyInitializationException
通过忽略来解决LazyInitializationException
这里有无数个问题,如何通过急切获取、保持事务开启、打开另一个事务、OpenEntityManagerInViewFilter等方式解决“无法初始化代理”问题。但是,是否可能简单地告诉Hibernate忽略这个问题,假装集合是空的?在我的情况下,不提前获取它只意味着我不关心它。
实际上,这是一个XY问题,具体问题如下:
我有以下类:
class Detail { @ManyToOne(optional=false) Master master; ... } class Master { @OneToMany(mappedBy="master") Listdetails; ... }
并且想要处理两种请求:一种返回带有所有`details`的单个`master`,另一种返回不带`details`的`master`列表。结果通过Gson转换为JSON。
我尝试了`session.clear`和`session.evict(master)`,但它们不会影响代替`details`的代理对象。有效的方法是:
master.setDetails(nullOrSomeCollection)
这种方法感觉有点不正规。我更希望有一种“忽略”问题的方式,因为它可以普遍适用,而不需要知道哪些部分是代理的。
编写一个忽略`initialized=false`的`AbstractPersistentCollection`实例的Gson `TypeAdapter`可能是一种方法,但这将依赖于`org.hibernate.collection.internal`,这肯定不是一个好事。在`TypeAdapter`中捕获异常听起来也不好。
一些回答后的更新
我的目标不是“获取数据而不是异常”,而是“如何获取`null`而不是异常”。
Dragan提出了一个有效的观点,即忘记获取并返回错误数据比抛出异常更糟糕。但是有一个简单的解决方法:
- 只对集合执行此操作
- 从不对它们使用`null`
- 返回`null`而不是空集合来指示未获取的数据
这样,结果就不会被错误解释。如果我忘记获取某些东西,响应将包含无效的`null`。
解决LazyInitializationException via ignorance
LazyInitializationException是一个常见的错误,它在尝试访问延迟加载的数据时抛出。这个问题的出现是因为在访问延迟加载数据时,会触发数据库查询操作,但此时数据库连接已经关闭,导致无法获取数据。然而,我们可以通过一种名为“ignorance”的解决方法来解决这个问题。
首先,我们创建一个名为LazyLoader的接口,用于加载延迟加载的数据。接口定义如下:
public interface LazyLoader{ void load(T t); }
然后,在我们的Service类中实现这个接口,用于获取包含延迟加载数据的主实体列表。代码如下:
public class Service { ListgetWithDetails(LazyLoader loader) { // 从session获取masterList的代码 for(Master master:masterList) { loader.load(master); } } }
接下来,我们可以通过以下方法调用这个Service类来获取带有延迟加载数据的主实体列表:
Service.getWithDetails(new LazyLoader() { public void load(Master master) { for(Detail detail:master.getDetails()) { detail.getId(); // 这将加载延迟加载的数据 } } });
在Java 8中,我们还可以使用Lambda表达式来简化代码:
Service.getWithDetails((master) -> { for(Detail detail:master.getDetails()) { detail.getId(); // 这将加载延迟加载的数据 } });
此外,我们还可以使用session.clear()和session.evict(master)来解决这个问题。但这些方法似乎并不能解决“如何获取不带延迟加载数据的Master实体,且不抛出代理异常”的问题。
总结起来,通过使用LazyLoader接口和Service类,我们可以解决LazyInitializationException异常的问题。这种方法允许我们在获取延迟加载数据时指定加载操作,以避免在数据库连接关闭后仍然访问延迟加载数据而导致的异常。同时,我们还可以使用session.clear()和session.evict(master)方法来清除缓存和解除代理对象,以避免异常的发生。
在使用Hibernate时,有时会遇到LazyInitializationException异常。这是因为在使用懒加载时,当访问一个未初始化的属性时,Hibernate会抛出这个异常。为了解决这个问题,可以使用Hibernate公共API中的Hibernate.isInitialized方法来判断属性是否已初始化。可以在TypeAdapter中添加以下代码来处理这个问题:
if ((value instanceof Collection) && !Hibernate.isInitialized(value)) { result = new ArrayList(); }
这样,当属性未初始化时,就可以创建一个空的ArrayList来代替。然而,笔者认为这种方法并不是最好的解决方案。因为属性未初始化可能意味着忘记了获取数据,这样返回的数据可能是错误的,即使不抛出异常,服务的消费者也会认为该集合是空的。为了解决这个问题,可以使用DTO(数据传输对象)的方式。只需要定义一个DTO来表示服务的响应,并在事务上下文中填充数据(没有LazyInitializationException的问题),然后将DTO传递给框架进行转换(如JSON、XML等)。
// 定义一个DTO public class ResponseDTO { private Listcollection; // getter and setter } // 在事务上下文中填充数据 @Transactional public ResponseDTO getServiceResponse() { ResponseDTO responseDTO = new ResponseDTO(); List collection = // 从数据库中获取集合数据 responseDTO.setCollection(collection); return responseDTO; } // 将DTO转换为服务响应 ResponseDTO responseDTO = getServiceResponse(); // 转换为JSON格式 String jsonResponse = // 框架将responseDTO转换为JSON字符串
这样,通过使用DTO,可以避免LazyInitializationException异常,并确保返回正确的数据给服务的消费者。当然,也可以注册一个自定义的序列化器来解决这个问题。使用这种方法,可以更灵活地处理每个字段的序列化,而不是为每个字段单独处理。使用DTO或自定义序列化器,可以有效地解决LazyInitializationException异常的问题。
以上是解决LazyInitializationException异常的原因和解决方法的整理。
问题的原因是在使用Hibernate进行延迟加载时,父实体被从会话中清除(通过session.evict(master)),但与父实体相关联的子实体集合(details)没有被清除。这导致在尝试访问子实体集合时抛出了LazyInitializationException异常。
解决方法是在清除父实体之前,先手动清除与之关联的子实体集合。通过调用session.evict(details)来清除子实体集合的代理对象,即可解决LazyInitializationException异常。
以下是解决方法的代码示例:
session.evict(details); session.evict(master);
这样,在清除父实体之前,先清除了子实体集合的代理对象,避免了LazyInitializationException异常的发生。