好久的筆記了,趁剛好休息整理文檔,翻出這一部分,稍加整理后,就發上來給大家共享一下,希望對各位有所幫助。
關于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-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);
}
}
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 }
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的代碼:
我加了些注釋,大家很容易明白:
1 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 }
44
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 }
44
上面的代碼就不用多解釋了, 到現在已經很清楚知道Spring針對 Hibernate的Lazy問題是怎么解決的。
當然我們可以擴展到除Web服務以外,來實現解決lazy的問題。(我們自己來管理TransactionSynchronizationManager就可以了)
當然Spring針對 Hibernate(非JPA的實現)原理也是一樣,只是它針對的SessionFactory,也是由TransactionSynchronizationManager來統一管理。
最后如果大家如還有不清楚的,歡迎一起討論。
Good Luck!
Yours Matthew!