spring+hibernate连接泄漏之殇(一)

2014-11-24 10:18:50 · 作者: · 浏览: 0

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

文章比较长,如果你是遇到了问题正急求解决方案的话,可以先只读这一条:检查你的dao是否有直接使用session,修改为使用hibernateTemplate。
如果你希望更多的了解session, hibernateTemplate, transaction,请继续。

以下案例基于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.c