最近在项目中使用了Struts + Hibernate的组合,在session和事务管理上遇到了些问题,查阅了一些资料后,决定采用servlet的过滤器来解决管理问题。
主要修改了
HibernateSessionFactory.java(由原文件由myeclipse自动生成的)public class HibernateSessionFactory {
/**
* Location of hibernate.cfg.xml file. Location should be on the classpath
* as Hibernate uses #resourceAsStream style lookup for its configuration
* file. The default classpath location of the hibernate config file is in
* the default package. Use #setConfigFile() to update the location of the
* configuration file for the current session.
*/
private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
private static final ThreadLocal<Transaction> tLocaltx = new ThreadLocal<Transaction>();
private static Configuration configuration = new Configuration();
private static org.hibernate.SessionFactory sessionFactory;
private static String configFile = CONFIG_FILE_LOCATION;
private HibernateSessionFactory() {
}
/**
* Returns the ThreadLocal Session instance. Lazy initialize the
* <code>SessionFactory</code> if needed.
*
* @return Session
* @throws HibernateException
*/
public static Session getSession() throws HibernateException {
Session session = (Session) threadLocal.get();
if (session == null || !session.isOpen()) {
if (sessionFactory == null) {
rebuildSessionFactory();
}
session = (sessionFactory != null) ? sessionFactory.openSession() : null;
threadLocal.set(session);
}
return session;
}
/**
* Rebuild hibernate session factory
*
*/
public static void rebuildSessionFactory() {
try {
configuration.configure(configFile);
sessionFactory = configuration.buildSessionFactory();
} catch (Exception e) {
System.err.println("%%%% Error Creating SessionFactory %%%%");
e.printStackTrace();
}
}
/**
* Close the single hibernate session instance.
*
* @throws HibernateException
*/
public static void closeSession() throws HibernateException {
Session session = (Session) threadLocal.get();
threadLocal.set(null);
if (session != null) {
session.close();
}
}
/**
* return session factory
*
*/
public static org.hibernate.SessionFactory getSessionFactory() {
return sessionFactory;
}
/**
* return session factory
*
* session factory will be rebuilded in the next call
*/
public static void setConfigFile(String configFile) {
HibernateSessionFactory.configFile = configFile;
sessionFactory = null;
}
/**
* return hibernate configuration
*
*/
public static Configuration getConfiguration() {
return configuration;
}
/**
* 打开一个事务
*/
public static void beginTransaction() {
Transaction tx = tLocaltx.get();
try {
if (tx == null) {
tx = getSession().beginTransaction();
tLocaltx.set(tx);
}
} catch (Exception e) {
System.err.println("%%%% Error beginTransaction %%%%");
e.printStackTrace();
}
}
/**
* 关闭一个事务
*/
public static void commitTransaction() {
Transaction tx = tLocaltx.get();
try {
if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack()) {
tx.commit();
//一个事务结束就立即解除与tLocaltx的关联
tLocaltx.set(null);
}
} catch (Exception e) {
System.err.println("%%%% Error commitTransaction %%%%");
e.printStackTrace();
}
}
/**
* 事务回滚
*/
public static void rollbackTransaction() {
Transaction tx = tLocaltx.get();
try {
tLocaltx.set(null);
if (tx != null && !tx.wasCommitted() && !tx.wasRolledBack())
tx.rollback();
} catch (Exception e) {
System.err.println("%%%% Error rollbackTransaction %%%%");
e.printStackTrace();
}
}
}
在myec生成的HibernateSessionFactory源代码中,保证了在一次请求过程中共享单一的session实例,我们现在要加入的内容就是在一次请求中共享一个
Transaction实例,很明显,中文部分是增加的内容。添加的代码封装了事务的开始,提交以及回滚。这样子session和
Transaction实例可以跨越一次请求的多种方法,这有助于实现集合的延迟加载等Hibernate特性。
在用的时候,我们就应该使用封装后的事务方法和session方法,使用方法如下:
//获得唯一的session 实例
Session session = HibernateSessionFactory.getSession();
HibernateSessionFactory.beginTransaction();
//do something……(数据库操作如:添加、删除等)
HibernateSessionFactory.commitTransaction();
在我们的项目中,BaseDAO类封装了
Hibernate常用的数据库操作方法,其中HibernateSessionFactory.beginTransaction()已经一起封入了BaseDAO类,于是,我们便不用在使用代码中加入事务开始的方法,直接调用
BaseDAO类的方法即可。在调试过程中,如果我们需要测试数据是否能够写入数据库,就应该手工调用事务结束方法
HibernateSessionFactory.commitTransaction(),即可立即写入数据库。
注意:在一次业务逻辑中,只能用一次事务提交,也只需要一次事务提交,我们一般把事务提交放到语句执行的最后面。(如果你用了多次提交,只对第一次提交有效!)下面举例:
//testDAO 继承 BaseDAO
public class testDAO extends BaseDAO {
testDAO(){
super();
}
}
public class testAction {
public static void main(String[] args) {
ActionDAO ad = new ActionDAO();
ad.add(object);
// 手工调用事务提交,可将缓存中的数据立即写入数据库
HibernateSessionFactory.commitTransaction();
// 手工调用session关闭
HibernateSessionFactory.closeSession();
}
}
但在实际的应用中,我们应该把测试用的手工代码删除,因为事务和session的关闭还有事务的回滚是通过过滤器来完成的,当然过滤器需要servlet的支持,我们先来看看过滤器的代码:
public class CloseSessionFilter extends HttpServlet implements Filter {
/**
*
*/
private static final long serialVersionUID = 1L;
private FilterConfig filterConfig;
protected boolean enable; // 是否起用此过滤器
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
loadConfigParams();
}
public void loadConfigParams() { // 取得初始化参数
String enableStr = this.filterConfig.getInitParameter("enable");
if (enableStr.trim().equals("true")) {
this.enable = true;
} else {
this.enable = false;
}
}
// Process the request/response pair
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {
try {
filterChain.doFilter(request, response);
} catch (Exception sx) {
} finally {
if (enable) {
try {
HibernateSessionFactory.commitTransaction();
} catch (Exception e) {
HibernateSessionFactory.rollbackTransaction();
} finally {
HibernateSessionFactory.closeSession();
}
}
}
}
// Clean up resources
public void destroy() {
}
}
相应的web.xml配置:
<!-- 过滤器 settings: -->
<filter>
<filter-name>CloseSessionFilter</filter-name>
<filter-class>
com.struts.common.CloseSessionFilter
</filter-class>
<init-param>
<param-name>enable</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CloseSessionFilter</filter-name>
<servlet-name>action</servlet-name>
</filter-mapping>
需要注意的是多个过滤器之间的顺序位置,
filterChain.doFilter(request, response);是指调用下一个过滤器,也就是说,要调用完所有过滤器后,才会继续运行filterChain.doFilter(request, response);下面的内容,这是一个递归的过程。所以,在web.xml的配置中,我们要把Hibernate过滤器放在第一位,确保所有servlet运行完毕后,才调用最后的关闭方法。