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

微信扫码订阅
UP更新不错过~
关注
  • 0
    点赞
  • 0
    收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论 2

打赏作者

MatrixII

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值