spring的事务类型及spring和hibernate可能导致的问题分析
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
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只有一个。
java命名空间javax.swing类spring的类成员方法: minus定义及介绍 Spring消息通信 Spring Integration java命名空间javax.swing类spring的类成员方法: max定义及介绍 Spring社交扩展框架 Spring Social java命名空间javax.swing类spring的类成员方法: unset定义及介绍 Spring 同步解决方案 Spring Sync java命名空间javax.swing类spring的类成员方法: constant定义及介绍 可视化Spring开发插件 Spring IDE java命名空间javax.swing类spring的类成员方法: scale定义及介绍 Spring远程服务编程框架 Spring Remoting java命名空间javax.swing类spring的类成员方法: getpreferredvalue定义及介绍 Spring安全框架 Spring Security java命名空间javax.swing类spring的类成员方法: getmaximumvalue定义及介绍 读取spring配置文件的方法(spring读取资源文件) java命名空间javax.swing类spring的类成员方法: sum定义及介绍 Spring Tool Suite java命名空间javax.swing类spring的类成员方法: getvalue定义及介绍 Spring4Me java命名空间javax.swing类spring的类成员方法: width定义及介绍 spring-all java命名空间javax.swing类spring的类成员方法: height定义及介绍