Spring+Hibernate+MVC:Controller層中引入@Transaction對Service層設計的簡化
目前採用的分層設計(MVC)中,資料的持久化獲取主要都是在Service中完成的,而Controller主要通過呼叫Service的相應介面獲得Model,然後返回給View,這個模式對於設計來說是相當完善且被我們經常使用。
當我們引入Hibernate作為持久機制後,其採用的Object Map導航自動載入需要的資料時,對我們操作資料就更是便捷。
上面說了很多,可能與本文關聯不大,本文的主要欲說明的問題在於:
Controller層經常需要請求Service層相應的Model,且這些Model可能採用了Lazy載入機制,因此需要Service進行提前載入完畢後返回載入完畢的Model,Controller然後返回這個物件給View進行解析 ,程式碼和圖示如下:
A、實體定義1 MyChildOfObject.java
@Entity
@Table("t_myobject_child")
public class MyChildOfObject {
@Id @Column(name="child_id")
@GeneratedValue(generator="general_seq")
@AccessType("property")
private Long id;
@ManyToOne(fetch=FetchType.LAZY, optional=false)
@JoinColumn(name="myobject_id")
private MyObject parent;
...
}
A、實體定義2: MyObject.java
@Entity
@Table("t_myobject")
public class MyObject {
@Id @Column(name="myobject_id")
@GeneratedValue(generator="general_seq")
@AccessType("property")
private Long id;
@OneToMany(mappedBy="parent")
@Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE, CascadeType.DELETE_ORPHAN})
private Collection<MyChildOfObject> childs = new ArrayList<MyChildOfObject>();
...
public Collection<MyChildOfObject> getChilds() {
return this.childs;
}
...
}
B、Service定義:MyService.java
@Service
@Transactional(propagation=Propagation.SUPPORTS)
public class MyService {
private SessionFactory sessionFactory;
@Autowired
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public MyObject getMyObject(Long id) {
return (MyObject) this.sessionFactory.getCurrentSession().get(MyObject.class, id);
}
}
C、Controller定義:MyController.java
@Controller
@RequestMapping("/myobject.eric")
public class MyController {
private MyService myService;
private String childsListView;
@RequestMapping(params="action=listChilds")
public String renderChildsOfMyObject(@RequestParam("myobjectId") Long myobjectId
, HttpServletRequest request) throws Exceptions {
MyObject myObject = this.myService.getMyObject(myobjectId);
request.setAttribute("myObject", myObject);
return this.childsListView;
}
...
@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}
}
上述程式碼和圖示基本完成了工作,但在頁面顯示的時候會出現一個異常,提示MyObject的childs沒有載入,這個就是這次的問題所在了。
注:這裡的程式碼是基於Spring v2.5和Hibernate 3.2.x。
解決方案(一):修改實體定義,強制載入所有的childs
public class MyObject {
...
@OneToMany(mappedBy="parent", fetch=FetchType.EAGER)
@Cascade({CascadeType.SAVE_UPDATE, CascadeType.DELETE, CascadeType.DELETE_ORPHAN})
private Collection<MyChildOfObject> childs = new ArrayList<MyChildOfObject>();
...
}
點評:使用這個方式後,問題是解決了且很優雅,但也引入了一個最大的問題:效能,畢竟每次都載入全部的子物件可能不是每個應用都需要的。
解決方案(二):修改Service介面,提供一個可以返回包括子物件的介面
public class MyService {
...
public MyObject getMyObjectIncludeChilds(Long id) {
MyObject myObject = this.getMyObject(id);
// 載入子元素
myObject.getChilds().size;
}
}
點評:使用這個方式後,問題解決了但不夠優雅,畢竟以後類似的需求都需要核心Service增加相應的介面,最後可能導致這個核心Service乾的正活數量遠低於這些組裝工作。
解決方案(三):提供一個專門進行組裝用的Wrapper Service
public interfact IMyService {
public MyObject getMyObject(Long id);
}
public class MyService implements IMyService {
...
}
@Service("myServiceWrapper")
@Transactional(propagation=Propagation.SUPPORTS)
public class MyServiceWrapper implements IMyService {
private MyService myService;
@Autowired
public void setMyService(MyService myService) {
this.myService = myService;
}
public MyObject getMyObject(Long id) {
MyObject myObject = this.myService.getMyObject(id);
myObject.getChilds().size;
return myObject;
}
}
public class MyController {
private IMyService myService;
...
@Autowired
public void setMyService(@Qualifier("myServiceWrapper") IMyService myService) {
this.myService = myService;
}
}
點評:此方案基本就基於介面程式設計了(Spring極力推薦的),在確保核心Service介面不汙染的情況下,對需要的額外組裝功能完全交給Wrapper類來實現。這個方案對於有多種客戶端時(如即有基於Brower的b/s,又有Java Client)將非常有效,這也是區別與方案(四)的一個重要點;這個方案的主要缺點是類的數量會顯著上升。
解決方案(四):在Controller中引入@Transaction
原設計方案全部保留,唯一需要變更的是Controller的程式碼中加入@Transaction
public class MyController {
@RequestMapping(params="action=listChilds")
@Transactional(propagation=Propagation.SUPPORTS)
public String renderChildsOfMyObject(@RequestParam("myobjectId") Long myobjectId
, HttpServletRequest request) throws Exceptions {
...
}
}
點評:以非常小的變更(只是多了@Transactional宣告)就解決了問題,同時不產生效能影響又不汙染核心Service介面。這個方案的缺點在於A)、當存在多種客戶端時(如即有基於Brower的b/s,又有Java Client),此方案就失效了,這個時候可能要考慮方案(三)了;B)、在Controller層就接觸到了事務,不過由於我們使用了宣告式事務,對程式碼的影響基本無,同時又是配置的SUPPORTS方式,對Connection也沒有影響,還是處於可控狀態。
上述的幾種解決方案都能夠解決遇到的問題,同時也各有利弊,主要的關鍵是看哪種更適合我們當前的應用了。象我現在的專案,由於是完全基於b/s結構的,不存在異種客戶端,所以就直接上了方案(四)。