- 零、事务管理器运行过程
- 0.0、事务管理器如何判断当前有事务
- 一、Spring中事务的使用方式
- 1. 编程式事务
- 方式1:通过PlatformTransactionManager控制事务
- 方式2:通过TransactionTemplate来控制事务
- 2. 声明式事务
- 2.1 配置xml文件的方式
- 2.2 注解的方式
- 二、Spring中事务的7种传播行为案例
- 2.1 REQUIRED
- 场景一:外围没有事务
- 场景一:外围有事务
- 2.1 REQUIRED_NEW
- 场景一:外围没有事务
- 场景二:外围有事务
- 三、Spring多数据源的问题
- 案例一:
- 案例二
通过两个事务方法来说明一下:
service1方法:
@Transactional(transactionManager="transactionManager1",propagation = Propagation.REQUIRED)
public void m1(){
this.jdbcTemplate.update("insert into user1(username) VALUES (?)","aa");
service2.m2();
}
service2方法:
@Transactional(transactionManager="transactionManager1",propagation = Propagation.REQUIRED)
public void m2(){
this.jdbcTemplate.update("insert into user1(username) VALUES (?)","ss");}
spring事务中有个resources的ThreadLocal,static修饰的,用来存放共享的资源
private static final ThreadLocal
下面具体看一下简化版的事务过程:
1.TransactionInterceptor拦截m1方法
2.获取m1方法的事务配置信息:事务管理器名称和事务传播行为
3.从spring容器中找到事务管理器,然后判断当前上下文有没有事务,这时候显然没有
4.创建一个事务
//获取当前事务管理器的数据源
DataSource datasource1 = transactionManager1.getDataSource();
//获取当前数据源的连接
Connection conn = datasource1.getConnection();
//设置事务手动提交,开启事务
conn.setAutoCommit(false)
//将datasource1和conn存入map中
map.put(datasource1,conn)
//将map存入static的ThrealLocal中
resources.set(map);
5.执行this.jdbcTemplate.update();
6.jdbcTemplate内部需要获取连接,过程如下:
//先从上面的resources中获取map
Map map = resources.get();
//从map中获取连接,看有没有可用的连接
Connection conn = map.get(jdbaTemplate.getDatasources());
if(conn == null){
//如果没有找到连接,就重新获取一个;
conn = jdbcTemplate.datasource.getConnection();
}
7.执行db操作,进行插入
8.执行service2.m2()
9.m2方法上也有@Transactional, TransactionInterceptor拦截m2方法
10.同样获m2上的事务配置信息:事务管理器和传播行为
11.从spring中获取到事务管理器,transactionManager1和required,
然后判断当前上下文有没有事务,发现当前是有事务的,m1的事务正在进行,所以m2就加入了
12.执行this.jdbcTemplate.update();
13.jdbcTemplate内部需要获取连接,过程如下:
//先从上面的resources中获取map
Map map = resources.get();
//从map中获取连接,看有没有可用的连接
Connection conn = map.get(jdbaTemplate.getDatasources());
if(conn == null){
//如果没有找到连接,就重新获取一个;
conn = jdbcTemplate.datasource.getConnection();
}
14.执行db操作,进行插入
15.最终TransactionInterceptor发现两个方法都执行完毕,没有异常,执行事务提交
//获取当前事务管理器的数据源
DataSource datasource1 = transactionManager1.getDataSource();
//先从上面的resources中获取map
Map map = resources.get();
//从map中获取连接,看有没有可用的连接
Connection conn = map.get(datasource1);
//提交事务
conn.commit();
//管理链接
conn.close();
16.清理ThreadLocal中的连接,通过resources.remove(datasource1)将连接移除
17.清理事务
0.0、事务管理器如何判断当前有事务
一、Spring中事务的使用方式
1. 编程式事务
通过硬编码的方式使用spring中提供的事务相关的类来控制事务
方式1:通过PlatformTransactionManager控制事务步骤1:定义事务管理器PlatformTransactionManager
步骤2:定义事务属性TransactionDefinition
步骤3:开启事务
步骤4:执行业务操作
步骤5:提交 or 回滚
开启事务后,spring内部会执行一些操作
//有一个全局共享的threadLocal对象 resources static final ThreadLocal
将数据源datasource和connection映射起来放在了ThreadLocal中,ThreadLocal大家应该比较熟悉,用于在同一个线程中共享数据;后面我们可以通过resources这个ThreadLocal获取datasource其对应的connection对象。
为什么要扔进ThreadLocal ,原因是数据库实现事务是基于同一个数据库链接的,spring把他扔进ThreadLocal 就是为了保证同一个用事务注解的方法里面的所有sql执行时候拿来的数据库链接是同一个
- 准备maven依赖
org.springframework spring-jdbc 5.2.3.RELEASE org.springframework spring-tx 5.2.3.RELEASE
- 编写配置类,配置数据源和JdbcTemplate
@Configuration
@ComponentScans(
{@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
}
)
public class MyConfig {
@Bean
public DruidDataSource druidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&useSSL=false&userCharacterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("ronny@123456");
dataSource.setInitialSize(5);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
}
- 编写测试类
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private DruidDataSource dataSource;
public void update(User user) {
//1.获取事务管理器,指定数据源
PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
//2.定义事务属性(传播特性,隔离级别,超时时间,是否制度,回滚)
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//3.开启事务,返回事务状态
TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
try {
System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from user"));
int resilt = jdbcTemplate.update("insert into user(userId, username, password)value(?,?,?)",
user.getUserId(), user.getUsername(), user.getPassword());
System.out.println("新增行数"+resilt);
//4.提交事务
transactionManager.commit(transactionStatus);
} catch (DataAccessException e) {
//5.回滚事务
transactionManager.rollback(transactionStatus);
}
System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from user"));
}
}
- 运行输出
@Test
public void test(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
UserService userService = context.getBean("userService", UserService.class);
User user = new User(6,"ddd","1233121");
userService.doUpdate(user);
}
方式2:通过TransactionTemplate来控制事务
一、通过TransactionTemplate提供的方法执行业务操作主要有2个方法:(1).executeWithoutResult(Consumer action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作
transactionTemplate.executeWithoutResult(new Consumer() { @Override public void accept(TransactionStatus transactionStatus) { //执行业务操作 } });
(2). T execute(TransactionCallback action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作
Integer result = transactionTemplate.execute(new TransactionCallback() { @Nullable @Override public Integer doInTransaction(TransactionStatus status) { return jdbcTemplate.update("insert into t_user (name) values (?)", "executeWithoutResult-3"); } });
二、调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。那么什么时候事务会回滚,有2种方式:
(1)在execute或者executeWithoutResult内部执行transactionStatus.setRollbackonly();将事务状态标注为回滚状态,spring会自动让事务回滚
(2)execute方法或者executeWithoutResult方法内部抛出任意异常即可回滚
三、什么时候事务会提交?
方法没有异常 && 未调用过transactionStatus.setRollbackonly();
案例:
- 配置DataSource和JdbcTemplate,TransactionTemplate,DataSourceTransactionManager
@Configuration
@ComponentScans(
{@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
}
)
public class MyConfig {
@Bean
public DruidDataSource druidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&useSSL=false&userCharacterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("ronny@123456");
dataSource.setInitialSize(5);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource);
return transactionManager;
}
@Bean
public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager){
TransactionTemplate transactionTemplate = new TransactionTemplate();
transactionTemplate.setTransactionManager(transactionManager);
return transactionTemplate;
}
}
- 测试
@Service("userService")
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private JdbcTemplate jdbcTemplate;
public void bus1(){
this.transactionTemplate.executeWithoutResult(transactionStatus -> {
//先删除数据
this.jdbcTemplate.update("delete from user");
this.bus2();
});
}
private void bus2() {
this.transactionTemplate.executeWithoutResult(transactionStatus -> {
this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",7,"zzz","123");
this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",8,"pppp","123");
this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",9,"kkkk","123");
});
}
}
bus1中会先删除数据,然后调用bus2,此时bus1中的所有操作和bus2中的所有操作会被放在一个事务中执行,这是spring内部默认实现的,bus1中调用executeWithoutResult的时候,会开启一个事务,而内部又会调用bus2,而bus2内部也调用了executeWithoutResult,bus内部会先判断一下上线文环境中有没有事务,如果有就直接参与到已存在的事务中,刚好发现有bus1已开启的事务,所以就直接参与到bus1的事务中了,最终bus1和bus2会在一个事务中运行
2. 声明式事务 2.1 配置xml文件的方式
2.2 注解的方式
1、启用Spring的注释驱动事务管理功能
@EnableTransactionManagement
public class MainConfig4 {}
当spring容器启动的时候,发现有@EnableTransactionManagement注解,此时会拦截所有bean的创建,扫描看一下bean上是否有@Transaction注解(类、或者父类、或者接口、或者方法中有这个注解都可以),如果有这个注解,spring会通过aop的方式给bean生成代理对象,代理对象中会增加一个拦截器,拦截器会拦截bean中public方法执行,会在方法执行之前启动事务,方法执行完毕之后提交或者回滚事务。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@documented
@import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {
boolean proxyTargetClass() default false;
int order() default Ordered.LOWEST_PRECEDENCE;
}
2、定义事务管理器
@Bean
public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource);
return transactionManager;
}
3、需使用事务的目标上加**@Transaction**注解
- @Transaction放在接口上,那么接口的实现类中所有public都被spring自动加上事务、
- @Transaction放在类上,那么当前类以及其下无限级子类中所有pubilc方法将被spring自动加上事务
- @Transaction放在public方法上,那么该方法将被spring自动加上事务
注意:@Transaction只对public方法有效
4、执行db业务操作
如何确定方法有没有用到spring事务?
方式1:断点调试
TransactionAspectSupport中invokeWithinTransaction
方式2:看日志
spring处理事务的过程,有详细的日志输出,开启日志,控制台就可以看到事务的详细过程了
添加maven配置
ch.qos.logback logback-classic 1.2.3
srcmainresources新建logback.xml
[%d{MM-dd HH:mm:ss.SSS}][%thread{20}:${PID:- }][%X{trace_id}][%level][%logger{56}:%line:%method()]:%msg%n##########**********##########%n
控制台中日志:
[09-27 18:24:36.391][main: ][][DEBUG][o.s.beans.factory.support.DefaultListableBeanFactory:225:getSingleton()]:Creating shared instance of singleton bean 'druidDataSource' ##########**********########## [09-27 18:24:36.449][main: ][][DEBUG][o.s.beans.factory.support.DefaultListableBeanFactory:808:createArgumentArray()]:Autowiring by type from bean name 'jdbcTemplate' via factory method to bean named 'druidDataSource' ##########**********########## [09-27 18:24:36.499][main: ][][DEBUG][o.s.beans.factory.support.DefaultListableBeanFactory:225:getSingleton()]:Creating shared instance of singleton bean 'transactionManager' ##########**********########## [09-27 18:24:36.500][main: ][][DEBUG][o.s.beans.factory.support.DefaultListableBeanFactory:808:createArgumentArray()]:Autowiring by type from bean name 'transactionManager' via factory method to bean named 'druidDataSource' ##########**********########## [09-27 18:24:36.537][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:370:getTransaction()]:Creating new transaction with name [com.zjhc.service.UserService.doDelete]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager' ##########**********########## [09-27 18:24:37.811][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:267:doBegin()]:Acquired Connection [com.mysql.jdbc.JDBC4Connection@4dd6fd0a] for JDBC transaction ##########**********########## [09-27 18:24:37.813][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:285:doBegin()]:Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@4dd6fd0a] to manual commit ##########**********########## [09-27 18:24:37.850][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:958:update()]:Executing prepared SQL update ##########**********########## [09-27 18:24:37.851][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:643:execute()]:Executing prepared SQL statement [delete from user where userId = ?] ##########**********########## [09-27 18:24:37.929][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:740:processCommit()]:Initiating transaction commit ##########**********########## [09-27 18:24:37.929][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:330:doCommit()]:Committing JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@4dd6fd0a] ##########**********########## [09-27 18:24:37.962][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:389:doCleanupAfterCompletion()]:Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4dd6fd0a] after transaction ##########**********##########
简要分析:
@Transaction注解参数都是默认值,@Transaction注解中可以通过value或者transactionManager来指定事务管理器,但是没有指定,此时spring会在容器中按照事务管理器类型找一个默认的,刚好我们在spring容器中定义了一个,所以直接拿来用了。事务管理器我们用的是new DataSourceTransactionManager(dataSource),从事务管理器的datasource中获取一个数据库连接,然后通过连接设置事务为手动提交,然后将(datasource->这个连接)丢到ThreadLocal中了
从事务管理器的datasource中获取一个链接
开启connection手动提交事务
通过jdbcTemplate执行数据库操作
最后delete方法执行完毕之后,没有任何异常,那么spring就开始通过数据库连接提交事务了
只有同一个事务管理器的时候,才有者7种表现行为
1、事务管理器中的connection和jdbcTemplate操作db的connection如何使用同一个?
案例分析:
1、配置类
@Configuration
@ComponentScans(
{@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
}
)
@EnableTransactionManagement
public class MyConfig {
@Bean
public DruidDataSource druidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&useSSL=false&userCharacterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("ronny@123456");
dataSource.setInitialSize(5);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource);
return transactionManager;
}
}
2、创建三个Service类:
package com.zjhc.service;
import org.springframework.stereotype.Service;
@Service
public class Service1 {
@Autowired
private JdbcTemplate jdbcTemplate;
}
package com.zjhc.service;
import org.springframework.stereotype.Service;
@Service
public class Service2 {
@Autowired
private JdbcTemplate jdbcTemplate;
}
package com.zjhc.service;
import org.springframework.stereotype.Service;
@Service
public class TxService {
@Autowired
private Service1 service1;
@Autowired
private Service2 service2;
}
3、测试类
public class TransactionTest {
private TxService txService;
private JdbcTemplate jdbcTemplate;
//每次测试前先启动spring容器,并清理表数据
@Before
public void before(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
txService = context.getBean(TxService.class);
jdbcTemplate = context.getBean(JdbcTemplate.class);
jdbcTemplate.update("truncate table user");
jdbcTemplate.update("truncate table user1");
}
@After
public void after(){
System.out.println("user表中数据:"+jdbcTemplate.queryForList("select * from user"));
System.out.println("user1表中数据:"+jdbcTemplate.queryForList("select * from user1"));
}
}
2.1 REQUIRED
Service1:添加一个方法,事务传播特性为:required
@Transactional(propagation = Propagation.REQUIRED)
public void required(int id,String username,String password){
this.jdbcTemplate.update("insert into user(userId, username, password) VALUES (?,?,?)",id,username,password);
}
Service2:添加两个方法,事务传播特性为:required
@Transactional(propagation = Propagation.REQUIRED)
public void required(int id,String username,String password){
this.jdbcTemplate.update("insert into user1(userId, username, password) VALUES (?,?,?)",id,username,password);
}
@Transactional(propagation = Propagation.REQUIRED)
public void required_exception(int id,String username,String password){
this.jdbcTemplate.update("insert into user1(userId, username, password) VALUES (?,?,?)",id,username,password);
throw new RuntimeException();
}
场景一:外围没有事务
验证方法一:
//required
//场景一:外围没有事务,外围方法调用两个Required级别的方法
public void notransaction_exception_required_required(){
this.service1.required(1,"zs","111");
this.service2.required(1,"zs","111");
throw new RuntimeException();
}
测试:
@Test
public void test(){
txService.notransaction_exception_required_required();
}
结果:
验证方法二:
//required
//场景一:外围没有事务,外围方法调用两个Required级别的方法
public void notransaction_exception_required_required(){
this.service1.required(1,"zs","111");
this.service2.required_exception(1,"zs","111");
throw new RuntimeException();
}
测试:
@Test
public void test(){
txService.notransaction_exception_required_required();
}
结果:
TxService种添加方法:
测试方法一:
//场景二:外围开启事务,外围方法调用两个Required级别的方法
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_required(){
this.service1.required(1,"zs","111");
this.service2.required(1,"zs","111");
throw new RuntimeException();
}
测试:
@Test
public void test(){
txService.transaction_exception_required_required();
}
结果:
测试方法二:
//场景二:外围开启事务,外围方法调用两个Required级别的方法
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_exception_required_required(){
this.service1.required(1,"zs","111");
this.service2.required_exception(1,"zs","111");
throw new RuntimeException();
}
测试:
@Test
public void test(){
txService.transaction_exception_required_required();
}
结果:
Service1:添加一个方法,事务传播特性为REQUIRED_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void required_new(int id,String username,String password){
this.jdbcTemplate.update("insert into user(userId, username, password) VALUES (?,?,?)",id,username,password);
}
Service2:添加一个方法,事务传播特性为REQUIRED_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void required_new(int id,String username,String password){
this.jdbcTemplate.update("insert into user1(userId, username, password) VALUES (?,?,?)",id,username,password);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void required_new_exception(int id,String username,String password){
this.jdbcTemplate.update("insert into user1(userId, username, password) VALUES (?,?,?)",id,username,password);
throw new RuntimeException();
}
场景一:外围没有事务
验证方法一:
//required_new
//场景一:外围没有事务,外围方法调用两个Required级别的方法
public void notransaction_exception_required_requirednew(){
this.service1.required_new(1,"zs","111");
this.service2.required_new(1,"zs","111");
throw new RuntimeException();
}
测试:
@Test
public void test(){
txService.notransaction_exception_required_requirednew();
}
结果:
验证方法二:
//required_new
//场景一:外围没有事务,外围方法调用两个Required级别的方法
public void notransaction_exception_required_requirednew(){
this.service1.required_new(1,"zs","111");
this.service2.required_new_exception(1,"zs","111");
throw new RuntimeException();
}
测试:
@Test
public void test(){
txService.notransaction_exception_required_requirednew();
}
结果:
验证方法一:
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_new_exception_required_required(){
this.service1.required(1,"zs","111");
this.service2.required_new(1,"zs","111");
this.service2.required_new(2,"zs","222");
throw new RuntimeException();
}
测试:
@Test
public void test(){
txService.transaction_new_exception_required_required();
}
结果:
验证方法二:
//场景二:外围开启事务,外围方法调用两个Required_new级别的方法
@Transactional(propagation = Propagation.REQUIRED)
public void transaction_new_exception_required_required(){
this.service1.required(1,"zs","111");
this.service2.required_new(1,"zs","111");
this.service2.required_new_exception(2,"zs","222");
throw new RuntimeException();
}
测试:
@Test
public void test(){
txService.notransaction_exception_required_requirednew();
}
结果:
1、准备DB:2个库_ds1,ds2),每个库有2个表(user1,user2)
2、Spring配置类
源码:
@Configuration
@ComponentScans(
{@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
}
)
@EnableTransactionManagement
public class MyConfig {
@Bean
public DruidDataSource druidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&useSSL=false&userCharacterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("ronny@123456");
dataSource.setInitialSize(5);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource);
return transactionManager;
}
@Bean
public DruidDataSource druidDataSource1(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/test?useUnicode=true&useSSL=false&userCharacterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("ronny@123456");
dataSource.setInitialSize(5);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate1(DruidDataSource druidDataSource1){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(druidDataSource1);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager1(DruidDataSource druidDataSource1){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource1);
return transactionManager;
}
}
3、创建6个service类
@Service
public class Ds1User1Service {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
public void required(String name){
this.jdbcTemplate.update("insert into user(username) values (?)",name);
}
}
@Service
public class Ds1User2Service {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
public void required(String name){
this.jdbcTemplate.update("insert into user1(username) values (?)",name);
}
}
@Service
public class Ds2User1Service {
@Autowired
private JdbcTemplate jdbcTemplate1;
@Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
public void required(String name){
this.jdbcTemplate1.update("insert into test.user(username) values (?)",name);
}
}
@Service
public class Ds2User2Service {
@Autowired
private JdbcTemplate jdbcTemplate1;
@Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
public void required(String name){
this.jdbcTemplate1.update("insert into test.user1(username) values (?)",name);
}
}
@Service
public class Tx1Service {
@Autowired
private Ds1User1Service ds1User1Service;
@Autowired
private Ds1User2Service ds1User2Service;
}
@Service
public class Tx2Service {
@Autowired
private Ds2User1Service ds2User1Service;
@Autowired
private Ds2User2Service ds2User2Service;
}
4、创建测试方法
public class ManyDataSourceTest {
private Tx1Service tx1Service;
private JdbcTemplate jdbcTemplate;
private JdbcTemplate jdbcTemplate1;
@Before
public void before(){
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
this.tx1Service = context.getBean(Tx1Service.class);
this.jdbcTemplate = context.getBean("jdbcTemplate",JdbcTemplate.class);
this.jdbcTemplate1 = context.getBean("jdbcTemplate1",JdbcTemplate.class);
jdbcTemplate.update("truncate table user");
jdbcTemplate.update("truncate table user1");
jdbcTemplate1.update("truncate table test.user");
jdbcTemplate1.update("truncate table test.user1");
}
@After
public void after(){
System.out.println("ds1.user表数据:"+this.jdbcTemplate.update("select * from user"));
System.out.println("ds1.user1表数据:"+this.jdbcTemplate.update("select * from user1"));
System.out.println("ds2.user表数据:"+this.jdbcTemplate.update("select * from test.user"));
System.out.println("ds2.user1表数据:"+this.jdbcTemplate.update("select * from test.user1"));
}
}
5、代码验证
(1)场景一:
外围方法和内部方法使用相同的事务管理器,传播行为都是required
Tx1Service中:
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
public void test1(){
this.ds1User1Service.required(1,"张三");
this.ds1User2Service.required(1,"李四");
throw new RuntimeException();
}
| 方法 | 事务管理器 | 事务管理器对应数据源 | JdbcTemplate对应数据源 |
|---|---|---|---|
| test1 | transactionManager | dataSource | - |
| ds1User1Service.required | transactionManager | dataSource | dataSource |
| ds1User2Service.required | transactionManager | dataSource | dataSource |
测试代码:
@Test
public void test(){
tx1Service.test1();
}
运行输出:
| 数据库结果 | 分析 |
|---|---|
| 张三李四均未插入 | 外围方法和内部方法均使用同一个事务管理器,且事务管理器和jdbcTemplate使用的datasource是同一个,所以外围开启事务后,内部方法加入外围事务,外围方法报错导致事务回滚,所以内部方法也跟着回滚了 |
(2)场景二:
外围方法和内部方法使用不同的事务管理器,传播行为都是required
Tx1Service中:
@Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
public void test2(){
this.ds1User1Service.required(1,"张三");
this.ds1User2Service.required(1,"李四");
throw new RuntimeException();
}
| 方法 | 事务管理器 | 事务管理器对应数据源 | JdbcTemplate对应数据源 |
|---|---|---|---|
| test2 | transactionManager1 | dataSource1 | - |
| ds1User1Service.required | transactionManager | dataSource | dataSource |
| ds1User2Service.required | transactionManager | dataSource | dataSource |
测试代码:
@Test
public void test(){
tx1Service.test2();
}
结果
| 数据库结果 | 分析 |
|---|---|
| 张三李四均插入 | 外围方法和内部方法使用不同的事务管理器,内部方法在各自的事务中进行,不受外围事务方法的控制 |
场景三:
Tx1Service中加入:
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
public void test3(){
this.ds1User1Service.required(1,"张三");
this.ds1User2Service.required(1,"李四");
this.ds2User1Service.required(1,"王五");
this.ds2User2Service.required(1,"赵六");
throw new RuntimeException();
}
| 方法 | 事务管理器 | 事务管理器对应数据源 | JdbcTemplate对应数据源 |
|---|---|---|---|
| test3 | transactionManager | dataSource | - |
| ds1User1Service.required | transactionManager | dataSource | dataSource |
| ds1User2Service.required | transactionManager | dataSource | dataSource |
| ds2User1Service.required | transactionManager1 | dataSource1 | dataSource1 |
| ds2User2Service.required | transactionManager1 | dataSource1 | dataSource1 |
测试结果:
| 数据库结果 | 分析 |
|---|---|
| 张三李四均未插入 | 外围方法和内部方法使用同一个事务管理器,所以三个方法在同一个事务中进行,外围方法抛出回滚,内部方法也跟着回滚 |
| 王五赵六均插入 | 外围方法和内部方法使用不同的事务管理器,内部方法在各自的事务中进行,不受外围事务方法的控制 |
场景四:
Tx2Service中加入:
@Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
public void test1(){
this.ds2User1Service.required(1,"张三");
this.ds2User2Service.required(2,"李四");
}
Tx1Service中加入:
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
public void test4(){
this.ds1User1Service.required(1,"王五");
this.ds1User2Service.required(1,"赵六");
this.tx2Service.test1();
throw new RuntimeException();
}
| 方法 | 事务管理器 | 事务管理器对应数据源 | JdbcTemplate对应数据源 |
|---|---|---|---|
| test4 | transactionManager | dataSource | - |
| ds1User1Service.required | transactionManager | dataSource | dataSource |
| ds1User2Service.required | transactionManager | dataSource | dataSource |
| this.tx2Service.test1 | transactionManager1 | dataSource1 | |
| ds2User1Service.required | transactionManager1 | dataSource1 | dataSource1 |
| ds2User2Service.required | transactionManager1 | dataSource1 | dataSource1 |
运行结果:
场景五:
Tx2Service中加入:
@Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
public void test2(){
this.ds2User1Service.required(1,"张三");
this.ds2User2Service.required(2,"李四");
throw new RuntimeException();
}
Tx1Service中加入:
@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
public void test5(){
this.ds1User1Service.required(1,"王五");
this.ds1User2Service.required(1,"赵六");
this.tx2Service.test1();
}
| 方法 | 事务管理器 | 事务管理器对应数据源 | JdbcTemplate对应数据源 |
|---|---|---|---|
| test5 | transactionManager | dataSource | - |
| ds1User1Service.required | transactionManager | dataSource | dataSource |
| ds1User2Service.required | transactionManager | dataSource | dataSource |
| this.tx2Service.test2 | transactionManager1 | dataSource1 | |
| ds2User1Service.required | transactionManager1 | dataSource1 | dataSource1 |
| ds2User2Service.required | transactionManager1 | dataSource1 | dataSource1 |
运行结果:
结果分析:
四条数据均未插入,test5通过事务管理器transactionManager 开启了事务tm1,然后张三和李四加入了tm1。test2通过事务管理器transactionManager1 开启了事务tm2,王五和赵六加入了tm2;test2方法内部抛错tm2回滚,方法里面的王五赵六跟test2属于同一个事务管理器,是同一个事务,所以王五赵六插入失败。tm1也感知到了这个异常,两个事务都回滚。
同一个数据源,多个事务管理器,多个JdbcTemplate的情形。
@Configuration
@ComponentScans(
{@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
}
)
@EnableTransactionManagement
public class MyConfig {
@Bean
public DruidDataSource druidDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&useSSL=false&userCharacterEncoding=utf8");
dataSource.setUsername("root");
dataSource.setPassword("ronny@123456");
dataSource.setInitialSize(5);
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource);
return transactionManager;
}
@Bean
public JdbcTemplate jdbcTemplate1(DruidDataSource druidDataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(druidDataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager transactionManager1(DruidDataSource druidDataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource);
return transactionManager;
}
}



