`
jeff312
  • 浏览: 77313 次
  • 性别: Icon_minigender_1
  • 来自: 新加坡
社区版块
存档分类
最新评论

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

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

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

public class AppService {
    ...

    public void updateDataService() {
        Connection conn = MyTransactionManager.getCurrentConn();
        MyTransactionManager.beginTransaction();

        new DAO1().update1stBatch();
        new DAO3().update3rdBatch();

        MyTransactionManager.commitTransaction();
        MyTransactionManager.closeConn(conn);
    }

    ...
}

public class DAO1 {
    ...

    public void update1stBatch() {
        Connection conn = MyTransactionManager.getCurrentConn();
        ...
        new DAO2().update2ndBatch();
    }
}

public class DAO2 {
    ...

    public void update2ndBatch() {
        Connection conn = MyTransactionManager.getCurrentConn();
        ...
    }
}

public class DAO3 {
    ...

    public void update3rdBatch() {
        Connection conn = MyTransactionManager.getCurrentConn();
        ...
    }
}


    在上面代码中,我们从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)不能随着线程自然销毁,其问题是显而易见的,比如下面的代码:
public class AppService {
    ...

    public void updateDataService() {
        //获取单例类的实例
        MyTransactionManager transMgr = transMgr.getInstance();
        
        Connection conn = transMgr.getCurrentConn();
        transMgr.beginTransaction();

        new DAO1().update1stBatch();
        new DAO3().update3rdBatch();

        transMgr.commitTransaction();
        transMgr.closeConn(conn);
    }

    ...
}

//这是一个单例类
public class MyTransactionManager {
    //具体的ThreadLocal实现子类,用泛型指定管理对象是java.sql.Connection类
    private ConnectionThreadLocal currentConnManager;
    
    public MyTransactionManager getInstance() {...}

    public Connection getCurrentConn() {
        Connection curConn = currentConnManager.get();
        if (curConn == null) {
            curConn = openConn();
            //不必须但推荐,如ThreadLocal对象总能随着线程自然销毁就可以不用
            currentConnManager.set(curConn);
        }
        return curConn;
    }

    public Connection openNewConn() {...}

    public void closeConn(Connection conn) {
        try {
	    if (conn != null) {
		conn.close();
		conn = null;
                //不必须但推荐,如ThreadLocal对象总能随着线程自然销毁就可以不用
                currentConnManager.set(null);
	    }
	} catch (SQLException e) {
	    e.printStackTrace();
	}
    }
}


    上面的代码演示了,如果每次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)也不能省略),那么永远不会有事。
分享到:
评论
14 楼 jeff312 2009-10-16  
在采用ThreadLocal模式之前,我是用动态/静态 代理的AOP方式来管理Connection的,即用一个DBProxy类来代理getConn(),closeConn()之类的方法。比如在closeConn之前加入判断,如果该conn上还有未提交的事务,则自动提交后关闭conn(或不关闭conn,直到所有事务提交完毕,这取决与Proxy的策略)。
13 楼 jeff312 2009-10-16  
mikewang 写道
jeff312 写道
smilerain 写道
ThreadLocal事务控制 没关系,而且用ThreadLocal控制事务很不好。
Hibernate3 用ThreadLocal来处理session 是个败笔。

请问它是如何“败笔”的?如有“胜笔”,又在哪里?请指教。



   解决了connection的传递问题。 而且解决的相当完美。


  总是有人把它误解为和事物管理相关, 或者总是有人认为它参与了事物管理。


哈哈,这个“败”的好像不是Hibernate,而是人啊。
12 楼 mikewang 2009-10-16  
jeff312 写道
smilerain 写道
ThreadLocal事务控制 没关系,而且用ThreadLocal控制事务很不好。
Hibernate3 用ThreadLocal来处理session 是个败笔。

请问它是如何“败笔”的?如有“胜笔”,又在哪里?请指教。



   解决了connection的传递问题。 而且解决的相当完美。


  总是有人把它误解为和事物管理相关, 或者总是有人认为它参与了事物管理。
11 楼 jeff312 2009-10-09  
smilerain 写道
ThreadLocal事务控制 没关系,而且用ThreadLocal控制事务很不好。
Hibernate3 用ThreadLocal来处理session 是个败笔。

请问它是如何“败笔”的?如有“胜笔”,又在哪里?请指教。
10 楼 jeff312 2009-10-09  
pipilu 写道
是啥意思啊?没看明白。
你明明是在service层控制事务,为什么要说在用ThreadLocal管理事务?你给出的这个代码:
MyTransactionManager transMgr = transMgr.getInstance();  
          
Connection conn = transMgr.getCurrentConn();  
transMgr.beginTransaction();  

new DAO1().update1stBatch();  
new DAO3().update3rdBatch();  

transMgr.commitTransaction();  
transMgr.closeConn(conn); 

怎么管理事务?如果update1stBatch和update3rdBatch出错了,你在哪捕获并回滚呢?
你用ThreadLocal的原因能说的具体一些么?

看了mikewang的解答,明白了。真是表达方式上千差万别啊——管理事务...


请大家不要望文生义,ThreadLocal名字叫“线程本地变量”,但对于其管理的资源而言,不需要考虑线程,因为对它们而言永远只有一个线程,就是当前线程。楼上同学问得好,我讲的只是事务,与线程无关。

对于“管理事务”的表达,我承认有些不妥,或许改用 “用ThreadLocal帮助管理本地事务”更好些?另外,to pipilu:
这段代码我加上try catch不就可以回滚事务了?如下:
MyTransactionManager transMgr = transMgr.getInstance();  
          
Connection conn = transMgr.getCurrentConn();  
transMgr.beginTransaction();  

try {
    new DAO1().update1stBatch();  
    new DAO3().update3rdBatch();  
} catch (Exception e) {
    conn.rollback();
    e.printStatckTrace();
}


transMgr.commitTransaction();  
transMgr.closeConn(conn); 

这样,无论是DAO1, DAO3, 甚至DAO2的所有update操作都会回滚。

对于mikewang所说的分布式事务,自有复杂的JTA来解决,ThreadLocal模式只是低端产品线上的一个替代品,它本身也不是因事务而生的,准确地说,它管理的是Connection而不是事务。
9 楼 shenjianwangyi 2009-10-06  
楼主 你到底是想讲线程还是想讲事务啊
8 楼 pipilu 2009-10-04  
是啥意思啊?没看明白。
你明明是在service层控制事务,为什么要说在用ThreadLocal管理事务?你给出的这个代码:
MyTransactionManager transMgr = transMgr.getInstance();  
          
Connection conn = transMgr.getCurrentConn();  
transMgr.beginTransaction();  

new DAO1().update1stBatch();  
new DAO3().update3rdBatch();  

transMgr.commitTransaction();  
transMgr.closeConn(conn); 

怎么管理事务?如果update1stBatch和update3rdBatch出错了,你在哪捕获并回滚呢?
你用ThreadLocal的原因能说的具体一些么?

看了mikewang的解答,明白了。真是表达方式上千差万别啊——管理事务...
7 楼 mikewang 2009-10-04  
ghyghost 写道
smilerain 写道
ThreadLocal事务控制 没关系,而且用ThreadLocal控制事务很不好。
Hibernate3 用ThreadLocal来处理session 是个败笔。



请问这位前辈,哪种方式处理事务好?


看你的应用场景

1, 如果你只有一个resource 或者resource之间不需要事务协调, 那么直接通过ra使用resource本身提供的事务管理就可以了,这种方式也叫本地事务

2, 如果你有多个resource, 并且resource之间需要事务协调, 那么必须使用2pc协议, 在这种情况下,你必须要拥有一个RM, 并且通过这个RM 来协调各个ra 之间的事务关系。 这种方式也叫分布式事务,也叫全局事务。

没有那种事务方式比较好这种说法, 完全取决于你的应用场景。

THreadLoacal 和 事务没有任何关系, hibernate 将具有本地事务的ra(jdbc connection) 放在 ThreadLoaal中,企图有这种方式在线程级别同步多个ra之间的事务关系, 不符合2pc 协议, 也不能满足分布式事务的要求。具体反映在无法再多个ra上进行事务协调,后面的事务失败了, 前面的已经提交的事务无法回滚。这将导致各个ra所对应的resource上的数据发生不一致,而且事务补偿方式也很难和这种方式集成。所以这个设计只能工作在对单一ra上的多次操作上。但是这种情况通常来说并不需要使用ThreadLocal, 本地事务就可以工作的很好。


至于为什么要用ThreaLocal,通常是解决在分层设计中, 对于 ra 的处理通常比较棘手, 具体表现在跨层操作时候的参数传递上,例如
conn = open connection;

dao1.doOpt(conn);
dao2.doOpt(conn);

close conn;

1个业务操作需要访问同1个ra但要做多次操作 ,这种情况下, ra的跨层传递就变的棘手。

一种解决方式是,把conn 放在全局变量中,

global conn = open connection;

dao1.doOpt();
dao2.doOpt();

close conn;

这种方式确实可以解决一些问题, 至少在跨层操作时,避免了和业务无关的参数传递。但全局变量的使用并不是一个好的方式。所有了方式2

tl = new threadLocal();
conn = open connection;
tl.put(conn);
dao1.doOpt();
dao2.doOpt();
close conn;

这种方式避免了使用全局变量, 同时采用的合适的设计模式再加上一些aop 可以把这段代码在做精简。我想我不用再写下去了。
6 楼 ghyghost 2009-10-04  
smilerain 写道
ThreadLocal事务控制 没关系,而且用ThreadLocal控制事务很不好。
Hibernate3 用ThreadLocal来处理session 是个败笔。



请问这位前辈,哪种方式处理事务好?
5 楼 smilerain 2009-10-03  
ThreadLocal事务控制 没关系,而且用ThreadLocal控制事务很不好。
Hibernate3 用ThreadLocal来处理session 是个败笔。
4 楼 rainlife 2009-10-03  
danni505 写道

此外,ThreadLocal并不只是用来解决多线程共享资源隔离的,它对事务控制的贡献也不能磨灭,实际上本文正是侧重于后者的。

胡扯,ThreadLocal同事务控制搭什么界???
3 楼 caoyangx 2009-10-03  
                    
2 楼 jeff312 2009-10-02  
danni505 写道
感觉你的这个问题和ThreadLocal并没有直接关系!
你这个事业务上设计没有完备,而ThreadLocal是解决多线程间隔离共享资源,你的代码并没有显示出事ThreadLocal的问题!

我并没有说ThreadLocal有什么问题,而是有些时候我们容易使用不当。比如,当每个service方法的一次执行都独占一个活动线程,即使没有set(curConn)也是可用的,但是省略set()方法的潜在危险就是一旦发生两个以上的service方法在同一线程中被执行(也许是因为软件升级),这个bug很可能让你无所适从,由于你潜意识里已经认为set()方法是不必要的,遇到bug的时候也就未必想得起来。

此外,ThreadLocal并不只是用来解决多线程共享资源隔离的,它对事务控制的贡献也不能磨灭,实际上本文正是侧重于后者的。
1 楼 danni505 2009-10-01  
感觉你的这个问题和ThreadLocal并没有直接关系!
你这个事业务上设计没有完备,而ThreadLocal是解决多线程间隔离共享资源,你的代码并没有显示出事ThreadLocal的问题!

相关推荐

    java事务 - threadlocal

    当Java事务与ThreadLocal结合使用时,可以在不同的线程中维护各自的事务状态,比如在Spring框架中,每个线程的ThreadLocal可以存储一个TransactionStatus对象,这样就可以在线程内部管理当前事务的状态,而不会影响...

    ThreadLocal和事务

    总结来说,这个小型练习展示了如何在Java Web应用中有效地结合c3p0数据源、ThreadLocal和事务管理,以实现安全、高效的转账操作。ThreadLocal提供了线程安全的变量管理,c3p0优化了数据库连接的使用,而事务则保证了...

    myMvcWeb2.zip java MVC框架 Struts框架的思想,动态代理,线程管理对象ThreadLocal

    这个是对java技术的一个小总结吧,用到的大概技术有:MVC框架,加注解,Struts框架的思想,动态代理,线程管理对象ThreadLocal,Connection对象池,Properties文件读取,EL表达式,JSTL,JavaBean,Java访问MySQL...

    myMvcWeb2.rar Java实现

    MVC框架,加注解,Struts框架的思想,动态代理,线程管理对象ThreadLocal,Connection对象池,Properties文件读取,EL表达式,JSTL,JavaBean,Java访问MySQL数据库,增删改查... ---------------------------------...

    java调用oracle存储过程入门实例 增删改查

    总结来说,这个入门实例涵盖了使用Java通过JDBC调用Oracle存储过程进行CRUD操作的基本流程,以及利用DOM4J解析XML配置文件和`ThreadLocal`管理数据库连接的方法。这样的设计使得程序更加模块化,易于维护,同时也...

    DBUtils操作数据库以及事物的管理

    ### DBUtils操作数据库及事务管理详解 #### 一、DBUtils简介 DBUtils是一个轻量级的Java数据库访问工具类库,它简化了JDBC的使用步骤,使得开发者能够更方便地进行数据库操作。相比于传统的JDBC编程方式,DBUtils...

    JAVA 使用数据库连接池连接Oracle数据库全代码

    使用`BasicDataSource`的`getConnection`方法获取连接,并利用`ThreadLocal`保存当前线程与连接的关系。最后,在业务逻辑完成后,确保正确关闭数据库连接。 ```java // 创建ThreadLocal对象 private static ...

    java事务 - 模板设计模式

    总结来说,Java事务模板设计模式结合ThreadLocal,提供了一种高效、健壮的事务管理策略。它减少了代码的重复性,提高了代码的可读性和可维护性,同时通过ThreadLocal解决了并发环境下的事务隔离问题。理解并熟练应用...

    连接数据库

    private static ThreadLocal<Connection> tl = new ThreadLocal(); ``` 定义了一个名为`DBUtil`的类,该类包含了数据库连接的相关配置信息,如用户名、密码、URL以及驱动等。这里使用了`ThreadLocal`来确保每个...

    课程hibernate的事务和并发.pdf

    实现“session-per-request”模式需要拦截器,通常使用ServletFilter或ThreadLocal来管理Session。ThreadLocal变量可以绑定到处理请求的线程,使得代码能方便地访问Session,而事务上下文环境也可存储在ThreadLocal...

    Java事务总结.docx

    为了保证事务的一致性,可以利用`ThreadLocal`来封装`Connection`,确保在同一事务中的DAO类获取的是同一个`Connection`。 #### JTA事务 JTA(Java Transaction API)用于管理分布式事务。一个完整的JTA事务包括一...

    多种数据库连接配置(oracle\mysql\access)

    此外,使用ORM框架如Hibernate,可通过ThreadLocal模式管理Session,确保每个线程有独立的Session,从而避免并发问题。 ### 总结 数据库连接配置是IT项目中的基础但关键环节,无论是Oracle、MySQL还是Access,合理...

    Hibernate开发必备版

    - `threadLocal`:使用`ThreadLocal`来存储当前线程的`Session`实例。 - `configuration`:`Configuration`对象用于读取配置文件,并根据这些配置信息构建`SessionFactory`。 - `sessionFactor`:`SessionFactory...

    Java jdbc三层及事务(转账案例)

    在Java JDBC中,我们可以使用Connection对象的setAutoCommit(false)方法来关闭自动提交,然后手动控制事务的开始(startTransaction)、提交(commit)和回滚(rollback)。例如,在转账操作中,如果两个账户的扣款...

    durid连接oracle-mysql-sqlserver操作文档

    本文档将详细讲解如何使用Durid来连接Oracle、Mysql和Sqlserver这三大主流数据库,并提供关于JDBC工具类的封装方法,以及在实际操作中可能遇到的问题及其解决策略。 首先,我们来看一下数据库的配置: 1. **MySQL...

    Jbuilder里配置Hibernate

    #### 三、总结 通过以上步骤,我们已经在JBuilder中成功地配置了Hibernate。需要注意的是,在实际开发过程中还需要根据具体的项目需求对配置文件进行相应的调整。此外,为了更好地管理和维护代码,建议将数据库连接...

    hadoop源码阅读总结

    ### Hadoop源码阅读总结:IPC/RPC 通信机制详解 #### 一、概述 Hadoop作为分布式计算框架,其内部各个组件之间的通信主要通过RPC(Remote Procedure Call)实现。本文将详细介绍Hadoop中RPC机制的工作原理,特别是...

    Spring事务管理高级应用难点剖析(4)

    总结来说,Spring通过ThreadLocal巧妙地解决了多线程环境下的事务管理和线程安全问题,使得单例Bean可以在多线程环境下正确、安全地工作。理解并熟练掌握这些高级应用,对于开发高并发、高性能的Spring应用至关重要...

    Apache的对象池化工具commons-pool

    - **Socket连接池**:在网络通信中,也可以使用对象池来管理Socket连接,减少网络I/O操作带来的性能损失。 #### 四、案例分析 假设我们正在开发一个基于Web的应用程序,需要频繁地访问数据库。传统的做法是在每次...

Global site tag (gtag.js) - Google Analytics