源代码解读Spring+Hibernate(JPA)的LazyLoadException异常

好久的笔记了,趁刚好休息整理文档,翻出这一部分,稍加整理后,就发上来给大家共享一下,希望对各位有所帮助。

关于LazyLoadException异常,使用过Hibernate O/R Mapping工具的人应该都遇到过,网上也是有很多解决的方案,其中Spring提供的一个方案就是在web.xml增加一个filter,示例代码如下:
<filter>  
<filter-name>entityManager</filter-name>
<filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>entityManagerFilter</filter-name>
<url-pattern>*.action</url-pattern>
</filter-mapping>


解决办法有了,接下来应该会有人好奇:这个配置filter后它是如何工作的?
下面来分析一下这个功能实现的源代码, 不过之前,比较重要的是了解,为何会出现lazyload exception的异常发生。

下面我模拟写了一段代码,这段代码就会发生该异常
注:只是为了说明,相关的代码就省略了。
@Entity
public class Room {

@Id
@Column(length=32)
private String id;

@Column(length=20)
private code;

@OneToMany(mappedBy="room") //default is use lazy load strategy
private Set desks;
}

@Entity
public class Desk {

@Id
@Column(length=32)
private String id;

@Column(length=20)
private code;

@ManyToOne
private Room root;

}

public class RoomSerivce {

@Transactional(readOnly=true)
public Room getRoomById(String roomId) {
Assert.notBlank(roomId, "room'id is null);
return getDao().findById(roomId);

}

}

 1 public class RoomServiceTest {
2
3 public static void main(String[] args[]) {
4
5 //get service from spring beanfactory
6 RoomService service = SpringContext.getSerivce("roomService");
7 Assert.notNull(service, " roomService bean not exsit");
8
9 Room room = service.getRoomById("1");
10 //here lazy exception throw out
11 Set Desks = room.getDesks();
12 CollectionsUtils.toString(Desks);
13 }
14 }

分析这段代码,我们不难发现,在RoomServiceTest这个测试的例子中,因为使用了基于Annotation的声明性事务,所以在 RoomSerivce.getRoomById方法运行结束后,事务就已经提交了。但示例中Room实体与Desk实例的关系使用的是lazy fetch的策略,此时Room对象中的desks集合还是为空。
当执行到下面两句时(这才真正使用到desks集合时)
Set Desks = room.getDesks();
CollectionsUtils.toString(Desks);
Hibernate就会根据原来lazy设定的方式,取EntityManager, 根据它从数据库查询 Desk实现的数据,这时上面我们已经提到,事务已经随getRoomById方法的运行结束提交. EntityManager对象也已经关闭。此时再调用 EntityManager操作,就会报EntityManager has been closed 异常(lazy load exception)

ok, 清楚这块,大家有时可能也猜想到了Spring这个解决方案是怎么处理的了。
Spring的TransactionInterceptor 其就是通过AOP负责拦截着所有针对事务TransactionManager的操作.
这样Spring就可以针对lazy异常进行拦截了。

清楚上面的后,下面的代码是非常好理解了,来看一下OpenEntityManagerInViewFilter的代码:
我加了些注释,大家很容易明白:
 protected void doFilterInternal(
2 HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
3 throws ServletException, IOException {
4
5 //通过WebApplicationContext,从Web服务中取得context实例后,根据EntityManagerFactory.class类型
6 //取得EntityManagerFacotry实例
7 EntityManagerFactory emf = lookupEntityManagerFactory(request);
8 boolean participate = false;
9
10 //如果静态方法hasResource已经有EntityManagerFactory实例了,就不用再通过
11 //EntityManagerFactory创建一个新EntityManger了
12 if (TransactionSynchronizationManager.hasResource(emf)) {
13 // Do not modify the EntityManager: just set the participate flag.
14 participate = true;
15 }
16 else {
17 logger.debug("Opening JPA EntityManager in OpenEntityManagerInViewFilter");
18 try {
19 //通过EntityManagerFactory创建一个新EntityManger,并通过bindResource方法
20 //保存到TransactionSynchronizationManager中
21 //这样,通TransactionSynchronizationManager的getResource方法取得EntityMangerHolder
22 EntityManager em = createEntityManager(emf);
23 TransactionSynchronizationManager.bindResource(emf, new EntityManagerHolder(em));
24 }
25 catch (PersistenceException ex) {
26 throw new DataAccessResourceFailureException("Could not create JPA EntityManager", ex);
27 }
28 }
29
30 try {
31 filterChain.doFilter(request, response);
32 }
33
34 finally {
35 if (!participate) {
36 //每次请求结束后,就把EntityManager关闭
37 EntityManagerHolder emHolder = (EntityManagerHolder)
38 TransactionSynchronizationManager.unbindResource(emf);
39 logger.debug("Closing JPA EntityManager in OpenEntityManagerInViewFilter");
40 EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
41 }
42 }
43 }

上面的代码就不用多解释了, 到现在已经很清楚知道Spring针对 Hibernate的Lazy问题是怎么解决的。
当然我们可以扩展到除Web服务以外,来实现解决lazy的问题。(我们自己来管理TransactionSynchronizationManager就可以了)

当然Spring针对 Hibernate(非JPA的实现)原理也是一样,只是它针对的SessionFactory,也是由TransactionSynchronizationManager来统一管理。

最后如果大家如还有不清楚的,欢迎一起讨论。

Good Luck!
Yours Matthew!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值