转自 http://www.uml.org.cn/j2ee/201211301.asp
spring基于ThreadLocal的“资源-事务”线程绑定设计的缘起
作者:bluishglc,发布于2012-11-30,来源:CSDN
题目起的有些拗口了,简单说,这篇文章想要解释Spring为什么会选择使用ThreadLocal将资源和事务绑定到线程上,这背后有着什么样的起因和设计动机,通过分析帮助大家更清晰地认识Spring的线程绑定机制。
“原始”的数据访问写法
访问任何带有事务特性的资源系统,像数据库,都有着相同的特点:首先你需要获得一个访问资源的“管道”,对于数据库来说,这个所谓的“管道”是JDBC里的Connection,是Hibernate里的Session.然后你会通过“管道”下达一系列的读写指令,比如数据库的SQL,最后你会断开这个“管道”,释放对这个资源的连接。在Spring里,用访问资源的“管道”来指代资源,因此JDBC的Connection和Hibernate的Session都被称之为“资源”(Resource)(本文会交替使用这两种称呼)。另一方面,资源与事务又有着紧密的关系,事务的开启与提交都是在某个“Resource”上进行的。以Hibernate为例,一种“原始”的数据访问程序往往会写成这样:
Session session = sessionFactory.openSession();//获取“资源”
Transaction tx = null;
try {
tx = session.beginTransaction(); //开始事务
....
DomainObject domainObject = session.load(...); //数据访问操作
....
domainObject.processSomeBusinessLogic();//业务逻辑计算
....
session.save(domainObject); //另一个数据访问操作
....
session.save(anotherDomainObject); //又一个数据访问操作
....
session.commit(); //提交事务
}
catch (RuntimeException e) {
tx.rollback();
throw e;
}
finally {
session.close(); //释放资源
}
上述代码的思路很直白:首先获得数据库“资源”,然后在该资源上开始一个事务,经过一系列夹杂着业务计算和数据访问的操作之后,提交事务,释放资源。
分层带来的困扰
相信很多人一下就能看出上面代码的问题:业务逻辑与数据访问掺杂在了一起,犯了分层的“忌讳”。一个良好的分层系统往往是这样实现上述代码的:使用Service实现业务逻辑,使用DAO向Service提供数据访问支持。
某个Service的实现类:
public class MyServiceImpl implements MyService {
public void processBusiness(){
//在这里获得资源并开启事务么?NO!会引入数据访问的API,“污染"Service,破坏了分层!
//Session session = sessionFactory.openSession();
//session.beginTransaction();
....
DomainObject domainObject = myDao.getDomainObject(...); //数据访问操作
....
domainObject.processSomeBusinessLogic();//业务逻辑计算
....
myDao.save(domainObject); //另一个数据访问操作
....
myDao.save(anotherDomainObject); //又一个数据访问操作
....
}
....
}
某个DAO的Hibernate实现类:
public class MyDaoHibernateImpl implements MyDao {
public void save(DomainObject domainObject){
//在这里获得资源并开启事务么?NO!你怎么确定这个方法一定是一个独立的事务
//而不会是某个事务的一部分呢?比如我们上面的Service。
//Session session = sessionFactory.openSession();
//session.beginTransaction();
....
session.save(domainObject);
}
....
}
矛盾的焦点
从“分层”的角度看,上述方案算是“完美”了,但却回避了一个现实的技术问题:如何安置“获取资源”(也就是session)和“开启事务”的代码呢?像代码中注释的那样,好像放在哪里都有问题,看上去像是一个“不可调和”的矛盾。如果要解决这个“不可调和”的矛盾,在技术上需要解决两个难题:
1.如何“透明”地进行事务定界(Transaction Demarcation)?
2.如何构建一个“上下文”,在事务开始与事务提交时,以及在事务过程中所有数据访问方法都能“隐式”地得到“同一个资源”(数据库连接/Hibernate Session)。所谓“隐式”是指不能把同一个资源实例用参数的方式传给数据访问方法,否则必然会出现数据访问层的上层代码受到数据访问专有API污染的问题(即破获了分层),而使用全局变量显然是不行的,因为全局变量是唯一的,没有哪个应用能容忍只使用一个数据库连接,对于一个用户请求一个线程的多线程Web应用环境更是如此。
Spring的解决之道
Spring使用基于AOP的声明式事务定界解决了第一个问题,而使用基于ThreadLocal的资源与事务线程绑定成功地解决了第二个问题。第一个问题所涉及源码主要是:
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的实例进行操作(这个动作由Spring提供的各种Template类实现)。
5.方法执行结束,同样通过AOP拦截,spring取出绑定到当前线程上的事务(对于hibernate来说就是取出绑定在当前线程上一个SessionHolder实例,它保存着当前的session与transaction实例),执行提交。
6.事务提交之后,释放资源,清空当前线程上绑定的所有对象!
7.如果该线程之后有新的事务发起,一切会重新开始,Spring会使用新的数据库连接或是hibernate session实例,开始新的事务,两个事务之间没有任何关系。
一个小小的总结
1.Connection-Per-Transaction/Session-Per-Transaction几乎总是你需要的。
分享到:
相关推荐
在实际应用中,理解ThreadLocal在Spring事务处理中的作用有助于优化并发性能和解决多线程环境下的事务问题。例如,如果线程之间需要共享数据,但又不想影响其他线程,ThreadLocal就是一个理想的选择。同时,也要注意...
结合ThreadLocal和Spring事务管理,我们可以在多线程环境中实现高效且一致的业务处理。例如,Spring在处理每个HTTP请求时,会为每个请求分配一个单独的线程。我们可以通过ThreadLocal来保存请求相关的状态信息,而...
Java事务和ThreadLocal是两种在Java编程中至关重要的概念,它们分别用于处理多线程环境下的数据一致性问题和提供线程局部变量。 首先,我们来深入理解Java事务。在数据库操作中,事务是一系列操作的集合,这些操作...
- **开始事务**:在处理请求的开始阶段,获取当前线程的ThreadLocal变量,初始化事务,并开启事务。 - **处理业务逻辑**:在这个阶段,所有的数据库操作都在开启的事务中进行,保证了事务的一致性。 - **提交或...
通过ThreadLocal,可以保证这些信息在Action的不同调用中保持一致,即在同一个请求的不同阶段中,即使使用不同的线程去处理,它们依然能访问到相同的上下文信息。 黄老邪,作为京东的资深架构师,拥有近20年的行业...
- **线程安全的配置对象**:在多层架构中,如Spring框架中,可以使用ThreadLocal来存储线程相关的配置信息,如数据库连接、事务管理等,确保这些对象不会被其他线程访问。 - **HTTP请求上下文**:在Web应用中,可以...
`SessionFactoryUtils.getSession`方法在获取Session时,会考虑当前是否存在有效的Session(例如在事务中),如果存在,则复用,否则创建新的Session。在事务开始时,Spring会创建一个Session并将其绑定到当前线程,...
Spring事务是指在Spring框架中对事务的支持和管理。事务是指一系列的操作,作为一个单元一起执行,如果其中任何一个操作失败,整个事务将被回滚。在Spring中,事务可以分为23类,以下是对这些类别的梳理和介绍: 1....
题目起的有些拗口了,简单说,这篇文章想要解释Spring为什么会选择使用ThreadLocal将资源和事务绑定到线程上,这背后有着什么样的起因和设计动机,通过分析帮助大家更清晰地认识Spring的线程绑定机制。访问任何带有...
Java中的ThreadLocal是一个非常重要的工具类,它在多线程编程中扮演着独特角色,尤其在处理线程间数据隔离和共享时。ThreadLocal不是线程本身,而是为每个线程提供一个独立的变量副本,使得每个线程都可以独立地改变...
"JAVA设计模式之事务处理"主要关注如何在业务逻辑中有效地管理和控制事务。 事务处理在企业级应用中至关重要,因为它确保数据的一致性和完整性。Java平台提供了Java Transaction API (JTA) 来处理全局事务,适用...
6.8.1. 在Spring中使用AspectJ来为domain object进行依赖注入 6.8.1.1. @Configurable object的单元测试 6.8.1.2. 多application context情况下的处理 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来...
AtomikosDataSource支持JTA事务,这意味着我们可以在同一个事务中操作多个数据源。Spring的`@Transactional`注解可以用来开启和管理这些分布式事务,确保事务的ACID特性。 7. **代码示例** 创建多数据源的配置...
ThreadLocal 在 Spring 中发挥着重要的作用,在管理 request 作用域的 Bean、事务管理、任务调度、AOP 等模块都出现了它们的身影,起着举足轻重的作用。要想了解 Spring 事务管理的底层技术,ThreadLocal 是必须...
在Spring框架中,ThreadLocal的应用非常广泛,尤其是在请求作用域的Bean管理、事务管理、任务调度和AOP等方面。例如,在Spring的DAO模板类中,尽管底层的数据连接或会话资源是非线程安全的,但是通过使用ThreadLocal...
6.8.1. 在Spring中使用AspectJ进行domain object的依赖注入 6.8.2. Spring中其他的AspectJ切面 6.8.3. 使用Spring IoC来配置AspectJ的切面 6.8.4. 在Spring应用中使用AspectJ加载时织入(LTW) 6.9. 更多资源 7...
在基于Spring的多数据源动态调用机制中,我们需要确保事务的一致性。我们可以使用Spring的事务管理机制来实现事务处理。例如,我们可以使用`@Transactional`注解来标注需要事务处理的方法。 ```java @Transactional...
通过以上介绍,我们可以了解到 `ThreadLocal` 在处理多线程环境中提供了独特的数据隔离机制,常用于创建线程安全的工具类实例,或者在每个线程内部保存全局变量。正确使用和管理 `ThreadLocal` 是避免潜在问题的关键...