当前位置:  编程语言>java/jsp

spring的事务类型及spring和hibernate可能导致的问题分析

 
    发布时间:2014-1-12  


    本文导语:  spring提供了以下的事务类型:1. PROPAGATION_REQUIRED:支持当前正在运行的事务,如果事务不存在则创建新的事务2. PROPAGATION_REQUIRES_NEW:永远创建新的事务,如果当前存在事务则先挂起该事务的执行。3. PROPAGATION_S...

  spring提供了以下的事务类型:

1.  PROPAGATION_REQUIRED:支持当前正在运行的事务,如果事务不存在则创建新的事务

2.  PROPAGATION_REQUIRES_NEW:永远创建新的事务,如果当前存在事务则先挂起该事务的执行。

3.  PROPAGATION_SUPPORTS:如果在事务上下文中执行,则支持当前存在的事务,否则不支持事务

4.  PROPAGATION_MANDATORY:方法必须参与事务,如果不存在事务,则抛出异常

5.  PROPAGATION_NESTED:困惑

6.  PROPAGATION_NOT_SUPPORTED:方法不参与事务,如果在事务上下文中运行,则挂起当前执行的事务

7.  

8.  PROPAGATION_NEVER:方法在不参与事务,如果在事务上下文中运行,则抛出异常。

   数据库连接泄漏是件可怕的事情,可能直接导致系统停止响应,另外因事务管理错误而导致数据出现不一致也是件可怕的事情,在后台系统中,这两个问题经常会结伴出现,本文将通过案例详解使用Spring+Hibernate时可能导致问题的几种情况,希望对大家有所帮助。

   以下案例基于Struts2.3.1+Spring3.1.1+hibernate3.5.4完成,案例场景为对系统参数进行管理,涉及如下四个

Action PropertyAction
Service PropertyService/PropertyServiceImpl
DAO PropertyDAO(继承HibernateDaoSupport)
Entity SysProperty
  1. Action中直接调用dao,未使用getHibernateTemplate
在一个复杂的多层架构系统中,事务控制在service中完成是更合理的,不应交由view层来控制,本文假定你是这么做的。一般我们代码的处理顺序为action->service->dao,然而总会有人会打破这种逻辑,打破可以,但要注意如下问题。
假定Action中有如下代码:
1 @Autowired
2 private PropertyDAO propertyDAO;
3 @Override
4 public String execute() throws Exception {
5      model = propertyDAO.get(1L);
6      return null;
7 }

这里action直接访问了dao中的get方法,因事务配置在service层,因此这里是没有事务控制的。
接下来我们看dao中的get方法,假定你的定义如下:
1 public SysProperty get(Long id) {
2     return (SysProperty) getSession().get(SysProperty.class, id);
3 }

代码很简单,getSession是父类HibernateDaoSupport中提供的方法,这里直接通过实体Id get得到结果。
接下来发布一下系统,访问页面,好像一切OK,但是,再刷新几下看看,可能你的连接池配置的比较大,要刷新多次,最后你发会现你的系统停止了响应。更直接点,调用你采用的连接池中的相关API检测下当前连接占用情况,你会发现刷新一次,被占用连接增加一个,可怕的事情发生了。
小结:在无事务管理器控制的情况下,通过getSession()打开的session不会被关闭,这个session所占用的数据库连接也不会被释放。

接下来,来点更可怕的,假设你的action中是这样的:
1 private List
ids;
2 @Override
3 public String execute() throws Exception {
4   for(Long id: ids) {
5     results.add(propertydao.get(id));
6   }
7   return null;
8 }
9
这时尝试一次传入多个id进行请求,请求结束后检查下连接数,你发现传入多少ID,就有多少连接被占用,也就是说每一次对dao的get调用都会占用一个不可释放的连接。
小结:如果没有配置事务管理器且直接使用getSession得到session,每次getSession都会创建一个新的session,每个session占用一个新的数据库连接,session无线程级别的共享。

  2. Action中直接调用dao,使用getHibernateTemplate
  当然,你可能没遇到过前面的情况,因为你会在DAO中这样写代码:
1 public SysProperty get(Long id)

{
2     return getHibernateTemplate().get(getEntityClass(), id);
3 }
那么是否就一切OK呢?如果只是想解决连接泄漏这个问题的话,答案是Yes,尝试多次请求,检查下你的连接池状况,没有连接泄漏的情况出现。所以简单而可靠的办法就是不再直接使用getSession,而是使用getHibernateTemplate进行相应操作,当需要使用session时,可以用下面的方法:
getHibernateTemplate().execute(new HibernateCallback
() {
 public T doInHibernate(Session session) throws HibernateException {
   Query queryObject = session.createQuery(hql);
   //....
 }
});
But...如果你的系统真的出现了连接泄漏,可能你需要关注更多。
还是前面Action中根据ID循环查询的操作,在这个案例中,每一次Dao的get方法都将重复这样的逻辑:创建session,分配session一个连接,关闭session,释放session占用的连接,也就是说session不会在线程级别共享。让我们继续,进入第三种情况来进一步说明

  3. Service未合理配置事务管理器
  绝大部分情况下我们会给service配置事务管理器,可能是通过xml或是@Transactional注解,但并非配置了就OK了。看下面的例子:
@Transactional
public class PropertyServiceImpl implements PropertyService

{
   @Autowired
   private PropertyDAO propertyDAO;

   @PostConstruct
   public void init(){
       propertyDAO.get(1L);
   }
   //
}
当然,假定你的DAO还是写成了这样:
public SysProperty get(Long id)

{
   return (SysProperty) getSession().get(SysProperty.class, id);
}
你期望在service初始化好后做一些数据库操作,你也给service配置了事务管理器,接下来你启动应用,然后检查连接池中的连接数,你会发现有连接未被释放!可能你会果断的修改dao中的方法:
public SysProperty get(Long id)

{
   return getHibernateTemplate().get(getEntityClass(), id);
}
然后你会理所当然的认为,即使配置了事务管理器,依然不能使用getSession(),事实可能并非如此。
我们调整一下代码,不在init中调用dao中的get方法,改为如下:
public SysProperty getProperty(Long id)

{
   return propertyDAO.get(id);
}
然后DAO继续使用(SysProperty) getSession().get(SysProperty.class, id),而action中的调用修改为:
@Autowired
private PropertyService propertyService;
@Override
public String execute() throws Exception

{
   model = propertyService.getProperty(1L);
   return null;
}
重新发布,调用,检查连接数,发现无被占用连接存在。
小结:如果正确配置了事务管理器,getSession是安全的。

这时要清楚spring是通过AOP来实现事务控制的,而@PostConstruct方法不会受AOP控制,因此上面的init方法等于无事务管理器。
那么再回头来说,是否只要dao中使用getHibernateTemplate就不会有问题呢?
假定service中的@PostConstruct方法如下:
@PostConstruct
public void init()

{
   SysProperty p = new SysProperty();
   p.setName("test_property_1");
   propertyDAO.save(p);
}
强调一下,前面已经提到这个方法不受spring的事务管理器控制。
假定DAO中的save方法如下:
public void save(SysProperty o)

{
   getSession().save(o);
}
启动一下应用,检查一下连接数,再检查一下数据是否有存储到数据库中。因为我们直接使用了getSession,因此连接不会释放,这点前面已经提到,但同时我们还将发现数据没有被存储,当然这个也好理解,因为上面已经提到这个方法未配置事务管理器。
小结:通过getSession().save()保存数据时,事务不会自动提交。

现在再修改下DAO中的save方法:
public void save(SysProperty o)

{
   getHibernateTemplate().save(o);
}
启动一下应用,检查一下连接数,再检查一下数据是否有存储到数据库中。因为我们使用hibernateTemplate,因此连接有释放,这点前面已经提到,但同时我们还发现数据也已存储到数据库中,说明hibernateTemplate会自动提交事务。
小结:如果未配置事务管理器,通过hibernateTemplate操作时,会自动创建并提交事务。

所以如果你觉得使用hibernateTemplate就OK了,那就要小心下面的代码了:
@PostConstruct
public void init()

{
 //1. 从你的账号A中扣除一万块
 //2. 这里的代码抛出了异常
 //3. 将你的账号B中增加一万块
}
如果上面的第2步出现了异常,那么因为1的事务已经提交,而3却没有执行,最终导致了数据的不一致,后果和连接泄漏一样严重!
除了@PostConstruct,还有其它原因会导致@Transactional无效,假定我们的service配置了事务管理器,但存在如下代码:
pubic void someServiceMethod()

{
 new Thread(){
   public void run(){
       doSomethingLater();
   }
 }
}

public void doSomethingLater(){
 //做一系列数据库操作
}
那么你可以去验证下doSomethingLater是否受事务管理器控制,事实上并不会,所以你需要理解spring AOP的机制,否则一个小坑会酿成灾难。

4. Service合理配置事务管理器

  最后补充一下,如果事务管理器配置正确的话会发生什么。这时不管你是用getSession还是getHibernateTemplate,结果都是一样,session将在thread级别共享,session只有一个。


相关文章推荐:


站内导航:


特别声明:169IT网站部分信息来自互联网,如果侵犯您的权利,请及时告知,本站将立即删除!

©2012-2021,,E-mail:www_#163.com(请将#改为@)

浙ICP备11055608号-3