该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2012-03-10
最后修改:2012-03-10
引述:Spring给我们带来的最大的一个便利是透明地实现事务管理,但是一般开发者仅仅是了解如何配置,在实际应用过程中却往往遇到很多问题,存在很多困惑。有有鉴于此,我特意总结了各种实际应用中遇到的Spring事务管理的困惑,这些文章摘自于我的《Spring 3.x企业应用开发实战》的第10章,我将通过连载的方式,陆续在此发出。欢迎大家讨论。 -------------------------------------------------------------------------------------------------------------------------- Spring通过单实例化Bean简化多线程问题 由于Spring的事务管理器是通过线程相关的ThreadLocal来保存数据访问基础设施(也即Connection实例),再结合IoC和AOP实现高级声明式事务的功能,所以Spring的事务天然地和线程有着千丝万缕的联系。 我们知道Web容器本身就是多线程的,Web容器为一个HTTP请求创建一个独立的线程(实际上大多数Web容器采用共享线程池),所以由此请求所牵涉到的Spring容器中的Bean也是运行于多线程的环境下。在绝大多数情况下,Spring的Bean都是单实例的(singleton),单实例Bean的最大好处是线程无关性,不存在多线程并发访问的问题,也就是线程安全的。 一个类能够以单实例的方式运行的前提是“无状态”:即一个类不能拥有状态化的成员变量。我们知道,在传统的编程中,DAO必须持有一个Connection,而Connection即是状态化的对象。所以传统的DAO不能做成单实例的,每次要用时都必须创建一个新的实例。传统的Service由于内部包含了若干个有状态的DAO成员变量,所以其本身也是有状态的。 但是在Spring中,DAO和Service都以单实例的方式存在。Spring是通过ThreadLocal将有状态的变量(如Connection等)本地线程化,达到另一个层面上的“线程无关”,从而实现线程安全。Spring不遗余力地将有状态的对象无状态化,就是要达到单实例化Bean的目的。 由于Spring已经通过ThreadLocal的设施将Bean无状态化,所以Spring中单实例Bean对线程安全问题拥有了一种天生的免疫能力。不但单实例的Service可以成功运行于多线程环境中,Service本身还可以自由地启动独立线程以执行其他的Service。 启动独立线程调用事务方法 package com.baobaotao.multithread; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Service; import org.apache.commons.dbcp.BasicDataSource; @Service("userService") public class UserService extends BaseService { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private ScoreService scoreService; public void logon(String userName) { System.out.println("before userService.updateLastLogonTime method..."); updateLastLogonTime(userName); System.out.println("after userService.updateLastLogonTime method..."); //scoreService.addScore(userName, 20);//①在同一线程中调用scoreService#addScore() //②在一个新线程中执行scoreService#addScore() Thread myThread = new MyThread(this.scoreService, userName, 20);//使用一个新线程运行 myThread.start(); } public void updateLastLogonTime(String userName) { String sql = "UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?"; jdbcTemplate.update(sql, System.currentTimeMillis(), userName); } //③负责执行scoreService#addScore()的线程类 private class MyThread extends Thread { private ScoreService scoreService; private String userName; private int toAdd; private MyThread(ScoreService scoreService, String userName, int toAdd) { this.scoreService = scoreService; this.userName = userName; this.toAdd = toAdd; } public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("before scoreService.addScor method..."); scoreService.addScore(userName, toAdd); System.out.println("after scoreService.addScor method..."); } } } 将日志级别设置为DEBUG,执行UserService#logon()方法,观察以下输出日志: 引用 before userService.logon method...
//①创建一个事务 Creating new transaction with name [com.baobaotao.multithread.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction … SQL update affected 1 rows after userService.updateLastLogonTime method... Initiating transaction commit //②提交①处开启的事务 Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction … Returning JDBC Connection to DataSource before scoreService.addScor method... //③创建一个事务 Creating new transaction with name [com.baobaotao.multithread.ScoreService.addScore]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT Acquired Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] for JDBC transaction … SQL update affected 0 rows Initiating transaction commit //④提交③处开启的事务 Committing JDBC transaction on Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] Releasing JDBC Connection [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] after transaction Returning JDBC Connection to DataSource after scoreService.addScor method... 在①处,在主线程(main)执行的UserService#logon()方法的事务启动,在②处,其对应的事务提交。而在子线程(Thread-2)执行的ScoreService#addScore()方法的事务在③处启动,在④处对应的事务提交。 所以,我们可以得出这样的结论: 在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,则不同线程下的事务方法工作在独立的事务中。 注:以上内容摘自《Spring 3.x企业应用开发实战》 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-03-11
那就是说Spring的事务管理是不能跨线程的吗??
|
|
返回顶楼 | |
发表时间:2012-03-11
最后修改:2012-03-11
分析得很深刻!LZ能否再详细介绍一下ThreadLocal的呢?
|
|
返回顶楼 | |
发表时间:2012-03-11
.NET中有一套事务机制。可以创建DependentTransaction,可以跨线程支持事务。
不知道Java有没有类似的机制或实现? |
|
返回顶楼 | |
发表时间:2012-03-12
只看蓝字。
|
|
返回顶楼 | |
发表时间:2012-03-12
跨线程的事务,估计要把线程做成阻塞式的。。。
|
|
返回顶楼 | |
发表时间:2012-03-13
qq123zhz 写道 跨线程的事务,估计要把线程做成阻塞式的。。。
具体要怎么弄呢,能否给出一个实例? |
|
返回顶楼 | |
发表时间:2012-03-13
这里怎么看不到事务配置的影子,难道是注解
|
|
返回顶楼 | |
发表时间:2012-03-13
superysy 写道 这里怎么看不到事务配置的影子,难道是注解
通过aop/tx进行事务增强: <aop:config proxy-target-class="true"> <aop:pointcut id="serviceJdbcMethod" expression="within(com.baobaotao.nestcall.BaseService+)"/> <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0"/> </aop:config> <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> |
|
返回顶楼 | |
发表时间:2012-03-13
请问能解释下文中提到的有状态和无状态的意思吗?
|
|
返回顶楼 | |