`
sony-soft
  • 浏览: 1105167 次
文章分类
社区版块
存档分类
最新评论

Spring基于ThreadLocal的“资源-事务”线程绑定设计的缘起

 
阅读更多

转自:http://blog.csdn.net/bluishglc/article/details/7784502

题目起的有些拗口了,简单说,这篇文章想要解释Spring为什么会选择使用ThreadLocal将资源和事务绑定到线程上,这背后有着什么样的起因和设计动机,通过分析帮助大家更清晰地认识Spring的线程绑定机制。本文原文链接:http://blog.csdn.net/bluishglc/article/details/7784502 转载请注明出处!


“原始”的数据访问写法


访问任何带有事务特性的资源系统,像数据库,都有着相同的特点:首先你需要获得一个访问资源的“管道”,对于数据库来说,这个所谓的“管道”是JDBC里的Connection,是Hibernate里的Session.然后你会通过“管道”下达一系列的读写指令,比如数据库的SQL,最后你会断开这个“管道”,释放对这个资源的连接。在Spring里,用访问资源的“管道”来指代资源,因此JDBC的Connection和Hibernate的Session都被称之为“资源”(Resource)(本文会交替使用这两种称呼)。另一方面,资源与事务又有着紧密的关系,事务的开启与提交都是在某个“Resource”上进行的。以Hibernate为例,一种“原始”的数据访问程序往往会写成这样:


  1. Session session = sessionFactory.openSession();//获取“资源”
  2. Transaction tx = null;
  3. try {
  4. tx = session.beginTransaction(); //开始事务
  5. ....
  6. DomainObject domainObject = session.load(...); //数据访问操作
  7. ....
  8. domainObject.processSomeBusinessLogic();//业务逻辑计算
  9. ....
  10. session.save(domainObject); //另一个数据访问操作
  11. ....
  12. myDao.save(anotherDomainObject); //又一个数据访问操作
  13. ....
  14. session.commit(); //提交事务
  15. }
  16. catch (RuntimeException e) {
  17. tx.rollback();
  18. throw e;
  19. }
  20. finally {
  21. session.close(); //释放资源
  22. }


上述代码的思路很直白:首先获得数据库“资源”,然后在该资源上开始一个事务,经过一系列夹杂着业务计算和数据访问的操作之后,提交事务,释放资源。


分层带来的困扰


相信很多人一下就能看出上面代码的问题:业务逻辑与数据访问掺杂在了一起,犯了分层的“忌讳”。一个良好的分层系统往往是这样实现上述代码的:使用Service实现业务逻辑,使用DAO向Service提供数据访问支持。


某个Service的实现类:

  1. public class MyServiceImpl implements MyService {
  2. public void processBusiness(){
  3. //在这里获得资源并开启事务么?NO!会引入数据访问的API,“污染"Service,破坏了分层!
  4. //Session session = sessionFactory.openSession();
  5. //session.beginTransaction();
  6. ....
  7. DomainObject domainObject = myDao.getDomainObject(...); //数据访问操作
  8. ....
  9. domainObject.processSomeBusinessLogic();//业务逻辑计算
  10. ....
  11. myDao.save(domainObject); //另一个数据访问操作
  12. ....
  13. myDao.save(anotherDomainObject); //又一个数据访问操作
  14. ....
  15. }
  16. ....
  17. }


某个DAO的Hibernate实现类:

  1. public class MyDaoHibernateImpl implements MyDao {
  2. public void save(DomainObject domainObject){
  3. //在这里获得资源并开启事务么?NO!你怎么确定这个方法一定是一个独立的事务
  4. //而不会是某个事务的一部分呢?比如我们上面的Service。
  5. //Session session = sessionFactory.openSession();
  6. //session.beginTransaction();
  7. ....
  8. session.save(domainObject);
  9. }
  10. ....
  11. }



矛盾的焦点


从“分层”的角度看,上述方案算是“完美”了,但却回避了一个现实的技术问题:如何安置“获取资源”(也就是session)和“开启事务”的代码呢?像代码中注释的那样,好像放在哪里都有问题,看上去像是一个“不可调和”的矛盾。如果要解决这个“不可调和”的矛盾,在技术上需要解决两个难题:

  1. 如何“透明”地进行事务定界(Transaction Demarcation)?
  2. 如何构建一个“上下文”,在事务开始与事务提交时,以及在事务过程中所有数据访问方法都能“隐式”地得到“同一个资源”(数据库连接/Hibernate Session)。所谓“隐式”是指不能把同一个资源实例用参数的方式传给数据访问方法,否则必然会出现数据访问层的上层代码受到数据访问专有API污染的问题(即破获了分层),而使用全局变量显然是不行的,因为全局变量是唯一的,没有哪个应用能容忍只使用一个数据库连接,对于一个用户请求一个线程的多线程Web应用环境更是如此。


Spring的解决之道

Spring使用基于AOP的声明式事务定界解决了第一个问题,而使用基于ThreadLocal的资源与事务线程绑定成功地解决了第二个问题。(关于spring的具体实现,可以参考我的另一篇文章:Spring源码解析(一) Spring事务控制之Hibernate ,第一个问题所涉及源码主要是:

org.springframework.aop.framework.JdkDynamicAopProxy 和 org.springframework.transaction.interceptor.TransactionInterceptor

第二个问题所涉及源码主要是:

org.springframework.transaction.support.AbstractPlatformTransactionManager 和 org.springframework.transaction.support.TransactionSynchronizationManager)


本文我们重点关注Spring是如何解决第二个问题的,对于这个问题有两点需要特别地解释:

  1. “上下文”:Spring使用的是“线程上下文”,也就是TreadLocal,原因非常简单,做为一种线程作用域变量,它能很好地被“隐式”获取,即在当前线程下可以直接得到该变量(避免了参数传递),同时又不会像全局变量那样作用域过大且全局只有一个实例。实际上,从更大的背景上来看,大多数的spring应用为B/S架构的web应用,受servlet线程模型的影响,此类web应用都是一个用户请求到达开启一个新的线程进行处理,在此背景下,spring这种以线程作为上下文绑定资源和事务的处理方式无疑是非常合适的。
  2. “资源与事务的生命周期”:如果只从“线程绑定”的字面上理解,很容易让人误解为绑定到线程上的资源和事务的生命周期与线程是等长的,这是错误的。实际上,资源和事务的生命周期与线程生命周期没有必然联系,只是当资源和事务存在时,它们会以TreadLocal的形式绑定到线程上而已。而资源的生命周期与事务的生命周期才是等长的,我们把资源-事务这种生命周期关系称为:Connection-Per-Transaction 或是 Session-Per-Transaction。


Hibernate自己动手丰衣足食

作为一小段插曲,我们聊聊Hibernate。大概是为满足对Session-Per-Transaction的普遍需求,Hibernate也实现了自己的Session-Per-Transaction模型,就是大家所熟知的SessionFactory.getCurrentSession(),该方法返回绑定在当前线程上session实例,若当前线程没有session实例,创建一个新的实例以ThreadLocal的形式绑定到当前线程上,同时,该方法生成的session其实是一个session代理,这个代理会对内部的实际session附加如下动作:

  1. 对session的数据操作方法进行拦截,确认在执行操作前已经调用过begainTranscation()开启了一个事务,否则会抛出异常。这一点确保了对session的使用必须总是从创建一个事务开始的。
  2. 当事务在commit或rollback后session会自动关闭。这一点确保了事务提交后session也将不可用。

正是这两点确保了Session与Transaction保持了一致的生命周期。

一切是这样进行的

结合上述场景和Spring的解决方案,一个使用了Spring声明性事务,实现了良好分层的程序,它的资源和事务在Spring的控制下是这样工作的:

  1. 若当前线程执行到了一个需要进行事务控制的方法(如某个service的方法),通过AOP拦截,spring会在方法执行前申请一个数据库连接或者一个hibernate session.
  2. 成功获得资源后,开启一个事务。
  3. 将资源也就是数据库连接或是hibernate session的实例存放于当前线程的ThreadLocal里(也就是进行所谓的线程绑定)
  4. 在方法执行过程中,任何需要获得数据库连接或是hibernate session进行数据访问的地方都会从当前线程的ThreadLocal里取出同一个数据库连接或是hibernate session的实例进行操作。
  5. 方法执行结束,同样通过AOP拦截,spring取出绑定到当前线程上的事务(对于hibernate来说就是取出绑定在当前线程上一个SessionHolder实例,它保存着当前的session与transaction实例),执行提交。
  6. 事务提交之后,释放资源,清空当前线程上绑定的所有对象!
  7. 如果该线程之后有新的事务发起,一切会重新开始,Spring会使用新的数据库连接或是hibernate session实例,开始新的事务,两个事务之间没有任何关系。


一个小小的总结

  1. Connection-Per-Transaction/Session-Per-Transaction几乎总是你需要的。
  2. 有时候,你希望能有这样一种变量:可以像全局变量那样随时随地的得到它,又不希望它拥有全局的作用域和唯一的一个实例,那么ThreadLocal可能正是你需要的.

分享到:
评论

相关推荐

    Spring事务处理-ThreadLocal的使用

    在实际应用中,理解ThreadLocal在Spring事务处理中的作用有助于优化并发性能和解决多线程环境下的事务问题。例如,如果线程之间需要共享数据,但又不想影响其他线程,ThreadLocal就是一个理想的选择。同时,也要注意...

    从ThreadLocal的使用到Spring的事务管理

    ThreadLocal是Java提供的一种线程绑定变量的工具类,它允许我们在一个线程内部创建独立的变量副本。每个线程都有自己的ThreadLocal实例,并且只能访问自己的副本,不会与其他线程的数据产生冲突,从而实现了线程间的...

    java事务 - threadlocal

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

    javaweb 通过threadlocal 手动提交事务

    ThreadLocal是Java提供的一种线程绑定变量的工具类,常用于解决多线程环境中的数据隔离问题。在本场景中,我们将讨论如何利用ThreadLocal来手动管理数据库事务。 1. **事务的基本概念** 事务是数据库操作的最小...

    ThreadLocal和事务

    ThreadLocal是Java提供的一个线程绑定变量的类,它为每个线程创建了一个独立的变量副本。这意味着每个线程都有自己的变量实例,不会相互影响。在Web应用中,尤其是在多线程环境中,ThreadLocal常用于存储线程相关的...

    04、导致JVM内存泄露的ThreadLocal详解-ev

    04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、导致JVM内存泄露的ThreadLocal详解_ev04、...

    java核心知识点学习----多线程间的数据共享和对象独立,ThreadLocal详解.pdf

    - 在并发量非常大或者资源有限的环境中,过多使用ThreadLocal可能导致线程池中的线程复用问题,使得新线程获取到旧线程的数据,这时应谨慎使用。 总之,ThreadLocal是Java中处理线程安全问题的一种有效手段,尤其...

    静态代理案例---线程买票

    在IT行业中,多线程是程序设计中的一个重要概念,尤其在服务器端开发、并发处理以及高性能计算中不可或缺。本案例“静态代理案例---线程买票”是一个典型的多线程应用,通过模拟售票过程,帮助我们理解如何在Java中...

    dynamic-datasource-spring-boot-starter_多数据源_

    3. **基于ThreadLocal**:利用线程局部变量,每个线程绑定一个数据源,这样在同一个线程中的所有操作都会使用相同的数据源。 4. **基于注解**:通过在方法或类上添加特定注解,指定该方法或类应该使用哪个数据源。 ...

    spring-security-core-2.0.5.RELEASE.src

    3. **SecurityContext**:存储当前用户的认证信息,通常与Spring的ThreadLocal机制结合使用。`org.springframework.security.core.context.SecurityContext`是主要接口,`org.springframework.security.core.context...

    ThreadLocal 线程本地变量 及 源码分析.rar_开发_设计

    - 容器内部使用:例如Spring框架中的TransactionTemplate就是利用ThreadLocal来管理事务的。 以上是对ThreadLocal的简单介绍和源码分析,实际使用中还需要结合具体业务场景进行合理设计和优化。通过深入理解...

    java ThreadLocal多线程专属的变量源码

    java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多线程专属的变量源码java ThreadLocal多...

    基于Spring+Ibatis的安全线程实现

    ThreadLocal是Java提供的一个线程绑定变量的工具类,可以保证每个线程都有自己独立的一份变量副本,避免了线程间的数据共享,这对于数据库连接等资源的管理非常有用,可以防止数据竞争问题。 在Ibatis中,通常我们...

    JAVA-多线程 所有文件

    在Java编程语言中,多线程是程序设计中的一个重要概念,尤其在开发高效能和响应迅速的应用时。这个“JAVA-多线程 所有文件”压缩包很可能包含了一系列关于Java多线程学习的源代码示例和相关文档。下面我们将深入探讨...

    ThreadLocal

    ThreadLocal是Java编程语言中的一个类,用于在多线程环境中提供线程局部变量。它是一种特殊类型的变量,每个线程都有自己的副本,互不影响,从而实现线程间数据隔离。ThreadLocal通常被用来解决线程共享数据时可能...

    spring3.x的读书笔记-6

    例如,Spring使用ThreadLocal来存储事务相关的信息,确保在同一个请求响应周期内,所有对象访问的ThreadLocal变量都是当前线程绑定的。 Spring的事务管理支持包括编程式和声明式两种方式。编程式事务管理通常使用...

    ThreadLocal原理及在多层架构中的应用

    - **线程安全的配置对象**:在多层架构中,如Spring框架中,可以使用ThreadLocal来存储线程相关的配置信息,如数据库连接、事务管理等,确保这些对象不会被其他线程访问。 - **HTTP请求上下文**:在Web应用中,可以...

Global site tag (gtag.js) - Google Analytics