目前采用的分层设计(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结构的,不存在异种客户端,所以就直接上了方案(四)。