概述
众所周知,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。
示例:启动独立线程调用事务方法
我们在MulitThreadService.java 在事务方法中启动独立线程运行另一个事务方法
配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
<context:component-scan base-package="com.xgj.dao.transaction.multiThreadinTrans" />
<!-- 使用context命名空间,引入数据库的properties文件 -->
<context:property-placeholder location="classpath:spring/jdbc.properties" />
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="${jdbc.driverClassName}"
p:url="${jdbc.url}"
p:username="${jdbc.username}"
p:password="${jdbc.password}"/>
<!-- 配置Jdbc模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
p:dataSource-ref="dataSource" />
<!--基于数据源的事务管理器,通过属性引用数据源 可以使用dataSource-ref引用不同的数据源,我们这里只展示同一个-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
p:dataSource-ref="dataSource"/>
<aop:config proxy-target-class="true">
<!-- 切点 -->
<aop:pointcut id="serviceMethod"
expression="within(com.xgj.dao.transaction.multiThreadinTrans.service.MulitThreadBaseService+)" />
<!-- 切面 -->
<aop:advisor pointcut-ref="serviceMethod"
advice-ref="txAdvice"
order="0" />
</aop:config>
<!-- 事务增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
</beans>
验证代码
package com.xgj.dao.transaction.multiThreadinTrans.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.xgj.dao.transaction.multiThreadinTrans.domain.Student;
/**
*
*
* @ClassName: MulitThreadService
*
* @Description: 继承抽象基类MulitThreadBaseService
*
* @author: Mr.Yang
*
* @date: 2017年9月26日 下午4:44:18
*/
@Service
public class MulitThreadService extends MulitThreadBaseService {
private AnotherService anotherService;
private StudentService studentService;
@Autowired
public void setStudentService(StudentService studentService) {
this.studentService = studentService;
}
@Autowired
public void setAnotherService(AnotherService anotherService) {
this.anotherService = anotherService;
}
/**
*
*
* @Title: executeLogic
*
* @Description: (1)在executeLogic方法体中启动一个独立的线程,
* 在该独立的线程中执行AnotherService#doAnotherThing ()方法
*
*
* @return: void
*/
public void executeLogic() {
System.out.println("logon method...");
// 调用本类的其他方法
method1();
// 调用其他类的方法,在同一个线程中调用anotherService#doAnotherThing(),将运行在同一个事务中
anotherService.doAnotherThing();
// 在一个新的线程中调用anotherService#doAnotherThing(),将启动一个新的事务
// 注意: 这里需要使用 extend Thread的方式 ,通过implements Runnable ,经验证不会开启新的事务
new MyThread().start();
// 必须休眠几秒,否则执行不到 新线程中的 方法
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// new MyThread1().run(); 这个会加到存在的事务中,原因暂时未知
}
public void method1() {
System.out.println("method1 begins");
System.out.println("模拟执行jdbc操作");
System.out.println("method1 finish");
}
/**
*
*
* @ClassName: MyThread
*
* @Description: 负责执行AnotherService#doAnotherThing()的线程
*
* @author: Mr.Yang
*
* @date: 2017年9月26日 下午4:50:29
*/
class MyThread extends Thread {
@Override
public void run() {
System.out.println("before studentService.updateSutdent method..."
+ anotherService.getClass().getName());
// anotherService.doAnotherThing();
Student student = new Student();
student.setName("1XXX");
student.setAge(112);
student.setSex("1B");
student.setStudentId(1);
studentService.updateSutdent(student);
System.out.println("after studentService.updateSutdent method...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class MyThread1 implements Runnable {
@Override
public void run() {
System.out.println("before studentService.updateSutdent method..."
+ anotherService.getClass().getName());
// anotherService.doAnotherThing();
Student student = new Student();
student.setName("XXX");
student.setAge(12);
student.setSex("B");
student.setStudentId(1);
studentService.updateSutdent(student);
System.out.println("after studentService.updateSutdent method..."
+ anotherService.getClass().getName());
}
}
}
单元测试
package com.xgj.dao.transaction.multiThreadinTrans.service;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MulitThreadServiceTest {
ClassPathXmlApplicationContext ctx = null;
MulitThreadService mulitThreadService = null;
@Before
public void initContext() {
// 启动Spring 容器
ctx = new ClassPathXmlApplicationContext(
"classpath:com/xgj/dao/transaction/multiThreadinTrans/conf_multiThreadTx.xml");
mulitThreadService = ctx.getBean("mulitThreadService",
MulitThreadService.class);
System.out.println("initContext successfully");
}
@Test
public void testNestedCallInOneTransaction() {
mulitThreadService.executeLogic();
}
@After
public void closeContext() {
if (ctx != null) {
ctx.close();
}
System.out.println("close context successfully");
}
}
关键日志分析
2017-09-26 18:17:25,890 DEBUG [main] (AbstractPlatformTransactionManager.java:367) - Creating new transaction with name [com.xgj.dao.transaction.multiThreadinTrans.service.MulitThreadService.executeLogic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2017-09-26 18:17:26,243 DEBUG [main] (DataSourceTransactionManager.java:248) - Acquired Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] for JDBC transaction
2017-09-26 18:17:26,248 DEBUG [main] (DataSourceTransactionManager.java:265) - Switching JDBC Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] to manual commit
logon method...
method1 begins
模拟执行jdbc操作
method1 finish
2017-09-26 18:17:26,291 DEBUG [main] (AbstractPlatformTransactionManager.java:476) - Participating in existing transaction
AnotherService#doAnotherThing executed
before studentService.updateSutdent method...com.xgj.dao.transaction.multiThreadinTrans.service.AnotherService$$EnhancerBySpringCGLIB$$1f0e2630
2017-09-26 18:17:26,311 DEBUG [Thread-1] (AbstractPlatformTransactionManager.java:367) - Creating new transaction with name [com.xgj.dao.transaction.multiThreadinTrans.service.StudentService.updateSutdent]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2017-09-26 18:17:26,375 DEBUG [Thread-1] (DataSourceTransactionManager.java:248) - Acquired Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] for JDBC transaction
2017-09-26 18:17:26,375 DEBUG [Thread-1] (DataSourceTransactionManager.java:265) - Switching JDBC Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] to manual commit
2017-09-26 18:17:26,393 DEBUG [Thread-1] (JdbcTemplate.java:869) - Executing prepared SQL update
2017-09-26 18:17:26,394 DEBUG [Thread-1] (JdbcTemplate.java:616) - Executing prepared SQL statement [update student set name = ? ,age = ? ,sex = ? where id = ?]
2017-09-26 18:17:26,535 DEBUG [Thread-1] (JdbcTemplate.java:879) - SQL update affected 1 rows
2017-09-26 18:17:26,541 INFO [Thread-1] (StudentDaoImpl.java:80) - updateStudent successfully
2017-09-26 18:17:26,542 DEBUG [Thread-1] (AbstractPlatformTransactionManager.java:759) - Initiating transaction commit
2017-09-26 18:17:26,542 DEBUG [Thread-1] (DataSourceTransactionManager.java:310) - Committing JDBC transaction on Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver]
2017-09-26 18:17:26,552 DEBUG [Thread-1] (DataSourceTransactionManager.java:368) - Releasing JDBC Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] after transaction
2017-09-26 18:17:26,552 DEBUG [Thread-1] (DataSourceUtils.java:327) - Returning JDBC Connection to DataSource
after studentService.updateSutdent method...
2017-09-26 18:17:31,311 DEBUG [main] (AbstractPlatformTransactionManager.java:759) - Initiating transaction commit
2017-09-26 18:17:31,311 DEBUG [main] (DataSourceTransactionManager.java:310) - Committing JDBC transaction on Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver]
2017-09-26 18:17:31,313 DEBUG [main] (DataSourceTransactionManager.java:368) - Releasing JDBC Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] after transaction
2017-09-26 18:17:31,313 DEBUG [main] (DataSourceUtils.java:327) - Returning JDBC Connection to DataSource
日志分析
Creating new transaction with name [com.xgj.dao.transaction.multiThreadinTrans.service.MulitThreadService.executeLogic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
执行executeLogic前,Spring开启了一个新的事务,
然后执行了
logon method...
method1 begins
模拟执行jdbc操作
method1 finish
我们在代码中调用
// 调用其他类的方法,在同一个线程中调用anotherService#doAnotherThing(),将运行在同一个事务中
anotherService.doAnotherThing();
日志显示,加入了现有的这个事务中
2017-09-26 18:17:26,291 DEBUG [main] (AbstractPlatformTransactionManager.java:476) - Participating in existing transaction
AnotherService#doAnotherThing executed
before studentService.updateSutdent method...com.xgj.dao.transaction.multiThre
然后 Initiating transaction commit 提交了当前的事务
紧接着,我们开启了一个新的线程
new MyThread().start();
日志显示,Spring开启了一个新的线程
before studentService.updateSutdent method...com.xgj.dao.transaction.multiThreadinTrans.service.AnotherService$$EnhancerBySpringCGLIB$$1f0e2630
2017-09-26 18:17:26,311 DEBUG [Thread-1] (AbstractPlatformTransactionManager.java:367) - Creating new transaction with name [com.xgj.dao.transaction.multiThreadinTrans.service.StudentService.updateSutdent]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
执行结束后,提交新开启的这个事务
Initiating transaction commit
结论
在相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,不同线程下的事务方法工作在独立的事务中。
示例源码
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster