关于用 ThreadLocal 管理 Connection 的一些总结

随着Hibernate3的流行,使用ThreadLocal管理事务的方式已然深入人心,在Hibernate3的项目里,如果不需要实现跨数据库的事务,使用Thread管理事务的效率比JTA这个庞然大物高很多,自然地成为了大家的首选。当然,既然ThreadLocal是JDK的一个基本实现(从JDK1.2起),它当然不独为Hibernate所有,即使我们只使用最基本的JDBC Connection,使用ThreadLocal Pattern也会给我们的系统设计带来许多好处。 

    所谓用Thread管理事务,就是始终保证当前线程只有一个在使用中的Connection(或Hibernate Session,以下如无特别说明,关于Connection的表述都可套用在Session上),而且只要有需要,我们总有办法拿到与当前线程绑定了的唯一的Connection,比如: 

Java代码   收藏代码
  1. public class AppService {  
  2.     ...  
  3.   
  4.     public void updateDataService() {  
  5.         Connection conn = MyTransactionManager.getCurrentConn();  
  6.         MyTransactionManager.beginTransaction();  
  7.   
  8.         new DAO1().update1stBatch();  
  9.         new DAO3().update3rdBatch();  
  10.   
  11.         MyTransactionManager.commitTransaction();  
  12.         MyTransactionManager.closeConn(conn);  
  13.     }  
  14.   
  15.     ...  
  16. }  
  17.   
  18. public class DAO1 {  
  19.     ...  
  20.   
  21.     public void update1stBatch() {  
  22.         Connection conn = MyTransactionManager.getCurrentConn();  
  23.         ...  
  24.         new DAO2().update2ndBatch();  
  25.     }  
  26. }  
  27.   
  28. public class DAO2 {  
  29.     ...  
  30.   
  31.     public void update2ndBatch() {  
  32.         Connection conn = MyTransactionManager.getCurrentConn();  
  33.         ...  
  34.     }  
  35. }  
  36.   
  37. public class DAO3 {  
  38.     ...  
  39.   
  40.     public void update3rdBatch() {  
  41.         Connection conn = MyTransactionManager.getCurrentConn();  
  42.         ...  
  43.     }  
  44. }  


    在上面代码中,我们从AppService里的方法的开头取得一个当前线程的connection并开启它的事务,并在方法的末尾提交这个事务(异常处理及回滚已省略)。设计的期望是,在这个方法里所有执行的DAO们的方法的数据库操作都应该处于该事务的管理之下。问题是每个dao的方法都需要首先拿到一个Connection才能工作,如果这个Connection不是service方法开头提到的那个,那么它就不能被纳入我们所开启的事务里了。 

让每个dao方法取得的Connection都与service方法里提到的Connection保持同一,是靠ThreadLocal来保证的。getCurrentConn()方法调用ThreadLocal变量的相关操作,获取当前执行线程中唯一的Connection对象,因此只要你的Connection是用getCurrentConn()方法获取的,那么每次拿到的都是那个家伙,dao嵌套也好(如dao2),绝不会错。 

至于ThreadLocal Pattern具体如何实现,网上有大把文章可读,我在这里想提的是一个比较容易出错的地方。一般说来,系统设计如果使用service层,那么该层的每一个方法都应该代表了一个执行线程的开始和结束,至少对于ThreadLocal变量来说是这样,也就是说,service层的方法应该在最开始创建ThreadLocal变量,并在结束前销毁它。这里的问题是,应用服务器(如JBoss)如何判定一个线程的终结的。我遇到的问题是,如果使用AJAX的HttpRequest发出请求,后台service层执行相关的方法,再把结果返回给AJAX的回调函数,那么应用服务器是会把该线程终结的,这样每次service方法执行前,都会新建一个ThreadLocal 的Connection对象并一直使用它直到service方法结束。但是在JSP下面就完全不同,如果你在浏览器里连续两次进入同一个jsp,线程就不会因为在两次jsp访问之间被销毁一次,也因此ThreadLocal变量(Connection)也就不能被自然销毁。 

当ThreadLocal变量(Connection)不能随着线程自然销毁,其问题是显而易见的,比如下面的代码: 
Java代码   收藏代码
  1. public class AppService {  
  2.     ...  
  3.   
  4.     public void updateDataService() {  
  5.         //获取单例类的实例  
  6.         MyTransactionManager transMgr = transMgr.getInstance();  
  7.           
  8.         Connection conn = transMgr.getCurrentConn();  
  9.         transMgr.beginTransaction();  
  10.   
  11.         new DAO1().update1stBatch();  
  12.         new DAO3().update3rdBatch();  
  13.   
  14.         transMgr.commitTransaction();  
  15.         transMgr.closeConn(conn);  
  16.     }  
  17.   
  18.     ...  
  19. }  
  20.   
  21. //这是一个单例类  
  22. public class MyTransactionManager {  
  23.     //具体的ThreadLocal实现子类,用泛型指定管理对象是java.sql.Connection类  
  24.     private ConnectionThreadLocal currentConnManager;  
  25.       
  26.     public MyTransactionManager getInstance() {...}  
  27.   
  28.     public Connection getCurrentConn() {  
  29.         Connection curConn = currentConnManager.get();  
  30.         if (curConn == null) {  
  31.             curConn = openConn();  
  32.             //不必须但推荐,如ThreadLocal对象总能随着线程自然销毁就可以不用  
  33.             currentConnManager.set(curConn);  
  34.         }  
  35.         return curConn;  
  36.     }  
  37.   
  38.     public Connection openNewConn() {...}  
  39.   
  40.     public void closeConn(Connection conn) {  
  41.         try {  
  42.         if (conn != null) {  
  43.         conn.close();  
  44.         conn = null;  
  45.                 //不必须但推荐,如ThreadLocal对象总能随着线程自然销毁就可以不用  
  46.                 currentConnManager.set(null);  
  47.         }  
  48.     } catch (SQLException e) {  
  49.         e.printStackTrace();  
  50.     }  
  51.     }  
  52. }  


    上面的代码演示了,如果每次ThreadLocal变量总是能在正确的时候自然销毁,那么closeConn()里的currentConnManager.set(null)是可以省略的,有趣的是,此时可以连getCurrentConn()里的currentConnManager.set(curConn)也可以省略,道理如下(不想看可以跳过): 
Connection curConn = currentConnManager.get()已经让curConn成为了ThreadLocal管理器(currentConnManager)中的成员的引用,如果此时还没有打开任何ThreadLocal的Connection,那么它引用的值为null,下面执行conn = openNewConn()之后,curConn也就是currentConnManager的成员就取得了一个具体对象的引用了,这跟set(curConn)的效果是一样的。 
    如果ThreadLocal变量不能自然销毁,而closeConn()里面又没有执行set(null),那么ThreadLocal变量仍将持有该Connection的引用,尽管它已经被关闭了(由于是DataSource提供的Connection,这个对象并不是真的被销毁成null了,尽管它已经不能用了,除非用DataSource.getConnection()将它再次激活)。这下问题就来了,如果你在同一线程中连续两次执行了AppService的updateDataService()方法(比如两次访问同一JSP),在第二次updateDataService()的开始,当其试图取得currentConn的时候,它拿到的却是那个已经被DataSource放逐了的、尚未再次激活的Connection。于是,无论接下来你要用这个Connection做什么事都会被服务器拒绝(JBoss下的错误消息:Connection is not associated with a managed Connection...),因为它不是激活状态下的Connection。 
    最后,无论你是否看懂了上面的文字表述,按照示例代码中的方式,老老实实地在closeConn()的时候为ThreadLocal变量set(null)(同时getCurrentConn()里的set(conn)也不能省略),那么永远不会有事。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值