多线程的困惑
由于Spring事务管理器是通过线程相关的ThreadLocal来保存数据访问基础设施再结合IOC和AOP实现高级声明式事务的功能所以Spring的事务天然地和线程有着千丝万缕的联系
我们知道Web容器本身就是多线程的Web容器为一个Http请求创建一个独立的线程所以由此请求所牵涉到的Spring容器中的Bean也是运行于多线程的环境下在绝大多数情况下Spring的Bean都是单实例的(singleton)单实例Bean的最大的好处是线程无关性不存在多线程并发访问的问题也即是线程安全的一个类能够以单实例的方式运行的前提是无状态即一个类不能拥有状态化的成员变量我们知道在传统的编程中DAO必须执有一个Connection而Connection即是状态化的对象所以传统的DAO不能做成单实例的每次要用时都必须new一个新的实例传统的Service由于将有状态的DAO作为成员变量所以传统的Service本身也是有状态的
但是在Spring中DAO和Service都以单实例的方式存在Spring是通过ThreadLocal将有状态的变量(如Connection等)本地线程化达到另一个层面上的线程无关从而实现线程安全Spring不遗余力地将状态化的对象无状态化就是要达到单实例化Bean的目的由于Spring已经通过ThreadLocal的设施将Bean无状态化所以Spring中单实例Bean对线程安全问题拥有了一种天生的免疫能力不但单实例的Service可以成功运行于多线程环境中Service本身还可以自由地启动独立线程以执行其它的Service下面通过一个实例对此进行描述
清单UserServicejava在事务方法中启动独立线程运行另一个事务方法
@Service(userService)
publicclassUserServiceextendsBaseService{
@Autowired
privateJdbcTemplatejdbcTemplate;
@Autowired
privateScoreServicescoreService;
//①在logon方法体中启动一个独立的线程在该独立的线程中执行ScoreService#addScore()方法
publicvoidlogon(StringuserName){
Systemoutprintln(logonmethod);
updateLastLogonTime(userName);
ThreadmyThread=newMyThread(thisscoreServiceuserName);
myThreadstart();
}
publicvoidupdateLastLogonTime(StringuserName){
Systemoutprintln(updateLastLogonTime);
Stringsql=UPDATEt_useruSETulast_logon_time=?WHEREuser_name=?;
jdbcTemplateupdate(sqlSystemcurrentTimeMillis()userName);
}
//②封装ScoreService#addScore()的线程
privateclassMyThreadextendsThread{
privateScoreServicescoreService;
privateStringuserName;
privateinttoAdd;
privateMyThread(ScoreServicescoreServiceStringuserNameinttoAdd){
thisscoreService=scoreService;
thisuserName=userName;
thistoAdd=toAdd;
}
publicvoidrun(){
scoreServiceaddScore(userNametoAdd);
}
}
}
将日志级别设置为DEBUG执行UserService#logon()方法观察以下输出的日志
清单执行日志
[main](AbstractPlatformTransactionManagerjava:)Creatingnewtransactionwithname
[usermultithreadUserServicelogon]:PROPAGATION_REQUIREDISOLATION_DEFAULT①
[main](DataSourceTransactionManagerjava:)AcquiredConnection
[monsdbcpPoolableConnection@]forJDBCtransaction
logonmethod
updateLastLogonTime
[main](JdbcTemplatejava:)ExecutingpreparedSQLupdate
[main](JdbcTemplatejava:)ExecutingpreparedSQLstatement
[UPDATEt_useruSETulast_logon_time=?WHEREuser_name=?]
[main](JdbcTemplatejava:)SQLupdateaffectedrows
[main](AbstractPlatformTransactionManagerjava:)Initiatingtransactioncommit
[Thread](AbstractPlatformTransactionManagerjava:)
Creatingnewtransactionwithname[usermultithreadScoreServiceaddScore]:
PROPAGATION_REQUIREDISOLATION_DEFAULT②
[main](DataSourceTransactionManagerjava:)CommittingJDBCtransaction
onConnection[monsdbcpPoolableConnection@]③
[main](DataSourceTransactionManagerjava:)ReleasingJDBCConnection
[monsdbcpPoolableConnection@]aftertransaction
[main](DataSourceUtilsjava:)ReturningJDBCConnectiontoDataSource
[Thread](DataSourceTransactionManagerjava:)AcquiredConnection
[monsdbcpPoolableConnection@dc]forJDBCtransaction
addScore
[main](JdbcTemplatejava:)ExecutingSQLstatement
[DELETEFROMt_userWHEREuser_name=tom]
[main](DataSourceUtilsjava:)FetchingJDBCConnectionfromDataSource
[Thread](JdbcTemplatejava:)ExecutingpreparedSQLupdate
[Thread](JdbcTemplatejava:)ExecutingpreparedSQLstatement
[UPDATEt_useruSETuscore=uscore+?WHEREuser_name=?]
[main](DataSourceUtilsjava:)ReturningJDBCConnectiontoDataSource
[Thread](JdbcTemplatejava:)SQLupdateaffectedrows
[Thread](AbstractPlatformTransactionManagerjava:)Initiatingtransactioncommit
[Thread](DataSourceTransactionManagerjava:)CommittingJDBCtransaction
onConnection[monsdbcpPoolableConnection@dc]④
[Thread](DataSourceTransactionManagerjava:)ReleasingJDBCConnection
[monsdbcpPoolableConnection@dc]aftertransaction
在①处在主线程(main)执行的UserService#logon()方法的事务启动在③处其对应的事务提交而在子线程(Thread)执行的ScoreService#addScore()方法的事务在②处启动在④处对应的事务提交
所以我们可以得出这样的结论在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中如果这些相互嵌套调用的方法工作在不同的线程中不同线程下的事务方法工作在独立的事务中
小结
Spring声明式事务是Spring最核心最常用的功能由于Spring通过IOC和AOP的功能非常透明地实现了声明式事务的功能一般的开发者基本上无须了解Spring声明式事务的内部细节仅需要懂得如何配置就可以了
但是在实际应用开发过程中Spring的这种透明的高阶封装在带来便利的同时也给我们带来了迷惑就像通过流言传播的消息最终听众已经不清楚事情的真相了而这对于应用开发来说是很危险的本系列文章通过剖析实际应用中给开发者造成迷惑的各种难点通过分析Spring事务管理的内部运作机制将真相还原出来在本文中我们通过剖析了解到以下的真相
◆在没有事务管理的情况下DAO照样可以顺利进行数据操作
◆将应用分成WebService及DAO层只是一种参考的开发模式并非是事务管理工作的前提条件
◆Spring通过事务传播机制可以很好地应对事务方法嵌套调用的情况开发者无须为了事务管理而刻意改变服务方法的设计
◆由于单实例的对象不存在线程安全问题所以进行事务管理增强的Bean可以很好地工作在多线程环境下
◆混合使用多种数据访问技术(如SpringJDBC+Hibernate)的事务管理问题
◆在通过Bean的方法通过SpringAOP增强存在哪些特殊的情况