1. 程式人生 > >Spring+Hibernate+MVC:Controller層中引入@Transaction對Service層設計的簡化

Spring+Hibernate+MVC:Controller層中引入@Transaction對Service層設計的簡化

Spring+Hibernate是目前Java應用開發中比較常見的組合,在開發WEB應用的時候,可能會結合一些其他的框架如Struts,這裡的介紹以Spring MVC為WEB框架。

目前採用的分層設計(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結構的,不存在異種客戶端,所以就直接上了方案(四)。