spring成神之路第四十六篇:Spring 如何管理多數據源事務?


本篇內容:通過原理和大量案例帶大家吃透Spring多數據源事務。

Spring中通過事務管理器來控制事務,每個數據源都需要指定一個事務管理器,如果我們的項目中需要操作多個數據庫,那么需要我們配置多個數據源,也就需要配置多個數據管理器。

多數據源事務使用2個步驟

1、為每個數據源定義一個事務管理器

如下面代碼,有2個數據源分別連接數據庫ds1和ds2,然后為每個數據源定義了1個事務管理器,此時spring容器中有2個數據源和2個事務管理器

//數據源1
@Bean
public DataSource dataSource1() {
     org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/ds1?characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("root123");
    dataSource.setInitialSize(5);
    return dataSource;
}
//事務管理器1,對應數據源1
@Bean
public PlatformTransactionManager transactionManager1(@Qualifier("dataSource1")DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
//數據源2
@Bean
public DataSource dataSource2() {
    org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/ds2?characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("root123");
    dataSource.setInitialSize(5);
    return dataSource;
}
//事務管理器2,對應數據源2
@Bean
public PlatformTransactionManager transactionManager2(@Qualifier("dataSource2")DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

2、指定事務的管理器bean名稱

使用@Transaction中時,需通過@Transaction注解的value或transactionManager屬性指定事務管理器bean名稱,如:

@Transactional(transactionManager ="transactionManager1", propagation =Propagation.REQUIRED)
publicvoid required(String name){
this.jdbcTemplate1.update("insert into user1(name) VALUES (?)", name);

這里補充一下,之前我們使用@Transactional的時候,並沒有通過value或者transactionManager設置事務管理器,這是為什么

這是因為我們在spring容器中只定義了一個事務管理器,spring啟動事務的時候,默認會按類型在容器中查找事務管理器,剛好容器中只有一個,就拿過來用了,如果有多個的時候,如果你不指定,spring是不知道具體要用哪個事務管理器的。

多數據源事務的使用就這么簡單,下面我們來看案例,案例才是精華。

事務管理器運行過程

這里先給大家解釋一下REQUIRED傳播行為下,事務管理器的大致的運行過程,方便理解后面的案例代碼。

Service1中:
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void m1(){
    this.jdbcTemplate1.update("insert into user1(name) VALUES ('張三')");
    service2.m2();
}
Service2中:
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void m2(){
    this.jdbcTemplate1.update("insert into user1(name) VALUES ('李四')");
}

spring事務中有個resources的ThreadLocal,static修飾的,用來存放共享的資源,稍后過程中會用到。

privatestaticfinalThreadLocal<Map<Object,Object>> resources =newNamedThreadLocal<>("Transactional resources");

下面看m1方法簡化版的事務過程:

1、TransactionInterceptor攔截m1方法
2、獲取m1方法的事務配置信息:事務管理器bean名稱:transactionManager1,事務傳播行為:REQUIRED
3、從spring容器中找到事務管理器transactionManager1,然后問一下transactionManager1,當前上下文中有沒有事務,顯然現在是沒有的
4、創建一個新的事務
    //獲取事務管理器對應的數據源,即dataSource1
    DataSource dataSource1 = transactionManager1.getDataSource();
    //即從dataSource1中獲取一個連接
    Connection conn = transactionManager1.dataSource1.getConnection();
    //開啟事務手動提交
    conn.setAutoCommit(false);
    //將dataSource1->conn放入map中
    map.put(dataSource1,conn);
    //將map丟到上面的resources ThreadLocal中
    resources.set(map);
5、下面來帶m1放的第一行代碼:this.jdbcTemplate1.update("insert into user1(name) VALUES ('張三')");
6、jdbctemplate內部需要獲取數據連接,獲取連接的過程
    //從resources這個ThreadLocal中獲取到map
    Map map = resources.get();
    //通過jdbcTemplate1.datasource從map看一下沒有可用的連接
    Connection conn = map.get(jdbcTemplate1.datasource);
    //如果從map沒有找到連接,那么重新從jdbcTemplate1.datasource中獲取一個
    //大家應該可以看出來,jdbcTemplate1和transactionManager1指定的是同一個dataSource,索引這個地方conn是不為null的
    if(conn==null){
        conn = jdbcTemplate1.datasource.getConnection();
    }
7、通過上面第6步獲取的conn執行db操作,插入張三
8、下面來到m1方法的第2行代碼:service2.m2();
9、m2方法上面也有@Transactional,TransactionInterceptor攔截m2方法
10、獲取m2方法的事務配置信息:事務管理器bean名稱:transactionManager1,事務傳播行為:REQUIRED
11、從spring容器中找到事務管理器transactionManager1,然后問一下transactionManager1,當前上下文中有沒有事務,顯然是是有的,m1開啟的事務正在執行中,所以m2方法就直接加入這個事務了
12、下面來帶m2放的第一行代碼:this.jdbcTemplate1.update("insert into user1(name) VALUES ('李四')");
13、jdbctemplate內部需要獲取數據連接,獲取連接的過程
    //從resources這個ThreadLocal中獲取到map
    Map map = resources.get();
    //通過jdbcTemplate1.datasource從map看一下沒有可用的連接
    Connection conn = map.get(jdbcTemplate1.datasource);
    //如果從map沒有找到連接,那么重新從jdbcTemplate1.datasource中獲取一個
    //大家應該可以看出來,jdbcTemplate1和transactionManager1指定的是同一個dataSource,索引這個地方conn是不為null的
    if(conn==null){
        conn = jdbcTemplate1.datasource.getConnection();
    }
14、通過第13步獲取的conn執行db操作,插入李四
15、最終TransactionInterceptor發現2個方法都執行完畢了,沒有異常,執行事務提交操作,如下
    //獲取事務管理器對應的數據源,即dataSource1
    DataSource dataSource1 = transactionManager1.getDataSource();
    //從resources這個ThreadLocal中獲取到map
    Map map = resources.get();
    //通過map拿到事務管理器開啟的連接
    Connection conn = map.get(dataSource1);
    //通過conn提交事務
    conn.commit();
    //管理連接
    conn.close();
16、清理ThreadLocal中的連接:通過map.remove(dataSource1)將連接從resource ThreadLocal中移除
17、清理事務

從上面代碼中可以看出:整個過程中有2個地方需要用到數據庫連接Connection對象,第1個地方是:spring事務攔截器啟動事務的時候會從datasource中獲取一個連接,通過這個連接開啟事務手動提交,第2個地方是:最終執行sql操作的時候,也需要用到一個連接。那么必須確保這兩個連接必須是同一個連接的時候,執行sql的操作才會受spring事務控制,那么如何確保這2個是同一個連接呢?從代碼中可以看出必須讓事務管理器中的datasource和JdbcTemplate中的datasource必須是同一個,那么最終2個連接就是同一個對象。

這里順便回答一下群友問的一個問題:什么是事務掛起操作?

這里以事務傳播行為REQUIRED_NEW為例說明一下,REQUIRED_NEW表示不管當前事務管理器中是否有事務,都會重新開啟一個事務,如果當前事務管理器中有事務,會把當前事務掛起。

所謂掛起,你可以這么理解:對當前存在事務的現場生成一個快照,然后將事務現場清理干凈,然后重新開啟一個新事務,新事務執行完畢之后,將事務現場清理干凈,然后再根據前面的快照恢復舊事務

下面我們再回到本文的內容,多數據源事務管理。

事務管理器如何判斷當前是否有事務?

簡化版的過程如下:

Map map=resource的ThreadLocal.get();
DataSource datasource = transactionManager.getDataSource();
Connection conn = map.get(datasource);
//如果conn不為空,就表示當前有事務
if(conn!=null){
}

從這段代碼可以看出:判斷是否存在事務,主要和datasource有關,和事務管理器無關,即使是不同的事務管理器,只要事務管理器的datasource是一樣的,那么就可以發現當前存在的事務。

事務管理器的運行過程和如何判斷是否有事務,這2點大家一定要理解,這個理解了,后面的案例理解起來會容易很多。

下面上案例。

案例源碼

git地址:
https://gitee.com/javacode2018/spring-series
本文案例對應源碼:
案例1:spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo7
案例2:spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo8

案例1

准備案例代碼

1、准備db

2個數據庫:ds1、ds2

每個庫中2個表:user1、user2

DROP DATABASE IF EXISTS ds1;
CREATE DATABASE if NOT EXISTS ds1;
USE ds1;
DROP TABLE IF EXISTS user1;
CREATE TABLE user1(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(64) NOT NULL DEFAULT '' COMMENT '姓名'
);
DROP TABLE IF EXISTS user2;
CREATE TABLE user2(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(64) NOT NULL DEFAULT '' COMMENT '姓名'
);
DROP DATABASE IF EXISTS ds2;
CREATE DATABASE if NOT EXISTS ds2;
USE ds2;
DROP TABLE IF EXISTS user1;
CREATE TABLE user1(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(64) NOT NULL DEFAULT '' COMMENT '姓名'
);
DROP TABLE IF EXISTS user2;
CREATE TABLE user2(
  id int PRIMARY KEY AUTO_INCREMENT,
  name varchar(64) NOT NULL DEFAULT '' COMMENT '姓名'
);

2、spring配置類

定義2個數據源:dataSource1、dataSource2,分別用來連接數據庫ds1和ds2

定義2個JdbcTemplate:jdbcTemplate1、jdbcTemplate2,分別關聯dataSource1和dataSource2

2個數據源對應2個事務管理器:transactionManager1、transactionManager2,分別用來管理2個數據源的事務

6個bean的名稱

數據源 JdbcTemplate 事務管理器
dataSource1 jdbcTemplate1 transactionManager1
dataSource2 jdbcTemplate2 transactionManager2

源碼如下:

package com.javacode2018.tx.demo7;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@EnableTransactionManagement //開啟spring事務管理功能
@Configuration //指定當前類是一個spring配置類
@ComponentScan //開啟bean掃描注冊
public class MainConfig7 {
    //定義數據源1,連接數據庫:ds1
    @Bean
    public DataSource dataSource1() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/ds1?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }
    //定義一個JdbcTemplate,對應數據源dataSource1,用來操作數據庫:ds1
    @Bean
    public JdbcTemplate jdbcTemplate1(@Qualifier("dataSource1") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    //定義事務管理器transactionManager1,對應數據源dataSource1,用來管理數據庫ds1中的事務
    @Bean
    public PlatformTransactionManager transactionManager1(@Qualifier("dataSource1") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    //定義數據源2,連接數據庫:ds2
    @Bean
    public DataSource dataSource2() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/ds2?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }
    //定義一個JdbcTemplate,對應數據源dataSource2,用來操作數據庫:ds2
    @Bean
    public JdbcTemplate jdbcTemplate2(@Qualifier("dataSource2") DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    //定義事務管理器transactionManager2,對應數據源dataSource2,用來管理數據庫ds2中的事務
    @Bean
    public PlatformTransactionManager transactionManager2(@Qualifier("dataSource2") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

下面來定義4個service,分別用來操作2個庫中的4張表。

3、Ds1User1Service

用來操作ds1.user1表,注意下面代碼中@Transactional注解中transactionManager的值為transactionManager1

package com.javacode2018.tx.demo7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class Ds1User1Service {
    @Autowired
    private JdbcTemplate jdbcTemplate1;
    @Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
    public void required(String name) {
        this.jdbcTemplate1.update("insert into user1(name) VALUES (?)", name);
    }
}

同樣,再來定義操作另外3個表的service

4、Ds1User2Service

用來操作ds1.user2表,事務管理器也是transactionManager1

package com.javacode2018.tx.demo7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class Ds1User2Service {
    @Autowired
    private JdbcTemplate jdbcTemplate1;
    @Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
    public void required(String name) {
        this.jdbcTemplate1.update("insert into user2(name) VALUES (?)", name);
    }
}

5、Ds2User1Service

用來操作ds2.user1表,對應事務管理器transactionManager2

package com.javacode2018.tx.demo7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class Ds2User1Service {
    @Autowired
    private JdbcTemplate jdbcTemplate2;
    @Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
    public void required(String name) {
        this.jdbcTemplate2.update("insert into user1(name) VALUES (?)", name);
    }
}

6、Ds2User2Service

用來操作ds2.user2表,對應事務管理器transactionManager2

package com.javacode2018.tx.demo7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class Ds2User2Service {
    @Autowired
    private JdbcTemplate jdbcTemplate2;
    @Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
    public void required(String name) {
        this.jdbcTemplate2.update("insert into user2(name) VALUES (?)", name);
    }
}

7、Tx1Service

package com.javacode2018.tx.demo7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Tx1Service {
    @Autowired
    private Ds1User1Service ds1User1Service;
    @Autowired
    private Ds1User2Service ds1User2Service;
}

8、Tx2Service

package com.javacode2018.tx.demo7;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class Tx2Service {
    @Autowired
    private Ds2User1Service ds2User1Service;
    @Autowired
    private Ds2User2Service ds2User2Service;
}

9、測試類Demo7Test

package com.javacode2018.tx.demo7;
import org.junit.After;
import org.junit.Before;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class Demo7Test {
    private Tx1Service txService1;
    private JdbcTemplate jdbcTemplate1;
    private JdbcTemplate jdbcTemplate2;
    //@Before標注的方法會在任意@Test方法執行之前執行,我們這在里清理一下2庫中4張表的數據
    @Before
    public void before() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig7.class);
        txService1 = context.getBean(Tx1Service.class);
        this.jdbcTemplate1 = context.getBean("jdbcTemplate1", JdbcTemplate.class);
        this.jdbcTemplate2 = context.getBean("jdbcTemplate2", JdbcTemplate.class);
        jdbcTemplate1.update("truncate table ds1.user1");
        jdbcTemplate1.update("truncate table ds1.user2");
        jdbcTemplate2.update("truncate table ds2.user1");
        jdbcTemplate2.update("truncate table ds2.user2");
    }
    //@After標注的方法會在任意@Test方法執行完畢之后執行,我們在此處輸出4張表的數據,用來查看測試案例之后,表中的數據清空
    @After
    public void after() {
        System.out.println("ds1.user1表數據:" + this.jdbcTemplate1.queryForList("SELECT * from user1"));
        System.out.println("ds1.user2表數據:" + this.jdbcTemplate1.queryForList("SELECT * from user2"));
        System.out.println("ds2.user1表數據:" + this.jdbcTemplate2.queryForList("SELECT * from user1"));
        System.out.println("ds2.user2表數據:" + this.jdbcTemplate2.queryForList("SELECT * from user2"));
    }
}

代碼驗證

1、場景1

外圍方法和內部方法使用相同的事務管理器,傳播行為都是REQUIRED

Tx1Service中添加代碼
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void test1() {
    this.ds1User1Service.required("張三");
    this.ds1User2Service.required("李四");
    throw new RuntimeException();
}

方法、事務管理器、事務管理器對應數據源、操作db的jdbctemplate中數據源對應關系。

方法 事務管理器 事務管理器對應數據源 jdbctemplate對應數據源
test1 transactionManager1 datasource1 -
ds1User1Service.required transactionManager1 datasource1 datasource1
this.ds1User2Service.required transactionManager1 datasource1 datasource1
Demo7Test中添加測試用例
@Test
public void test1() {
    this.txService1.test1();
}
運行輸出
ds1.user1表數據:[]
ds1.user2表數據:[]
ds2.user1表數據:[]
ds2.user2表數據:[]
結論分析
數據庫結果 結果分析
“張三”、“李四”均未插入 外圍方法和內部方法使用同一個事務管理器transactionManager1,且事務管理器和jdbctemplate的datasource都是同一個,外圍方法會開啟事務,內部方法加入外圍方法事務,外圍方法彈出異常導致事務回滾,內部方法跟着回滾了。

2、場景2

外部方法和內部方法使用不同的事務管理器

Tx1Service中添加代碼
@Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
public void test2() {
    this.ds1User1Service.required("張三");
    this.ds1User2Service.required("李四");
    throw new RuntimeException();
}

 

方法、事務管理器、事務管理器對應數據源、操作db的jdbctemplate中數據源對應關系。

方法 事務管理器 事務管理器對應數據源 jdbctemplate對應數據源
test2 transactionManager2 datasource2 -
ds1User1Service.required(“張三”); transactionManager1 datasource1 datasource1
this.ds1User2Service.required(“李四”); transactionManager1 datasource1 datasource1
Demo7Test中添加測試用例
@Test
public void test2() {
    this.txService1.test2();
}
運行輸出
ds1.user1表數據:[{id=1, name=張三}]
ds1.user2表數據:[{id=1, name=李四}]
ds2.user1表數據:[]
ds2.user2表數據:[]
結論分析
數據庫結果 結果分析
“張三”、”李四”均插入 外圍方法test2和內部兩個required方法用到的不是同一個事務管理器內部的2個方法在各自的事務中執行,不受外部方法事務的控制。

3、場景3

Tx1Service中添加代碼
@Autowired
private Ds2User1Service ds2User1Service;
@Autowired
private Ds2User2Service ds2User2Service;
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void test3() {
    this.ds1User1Service.required("張三");
    this.ds1User2Service.required("李四");
    this.ds2User1Service.required("王五");
    this.ds2User2Service.required("趙六");
    throw new RuntimeException();
}

方法、事務管理器、事務管理器對應數據源、操作db的jdbctemplate中數據源對應關系。

方法 事務管理器 事務管理器對應數據源 jdbctemplate對應數據源
test3 transactionManager1 datasource1 -
this.ds1User1Service.required(“張三”); transactionManager1 datasource1 datasource1
this.ds1User2Service.required(“李四”); transactionManager1 datasource1 datasource1
this.ds2User1Service.required(“王五”); transactionManager2 datasource2 datasource2
this.ds2User2Service.required(“趙六”); transactionManager2 datasource2 datasource2
Demo7Test中添加測試用例
@Test
public void test3() {
    this.txService1.test3();
}
運行輸出
ds1.user1表數據:[]
ds1.user2表數據:[]
ds2.user1表數據:[{id=1, name=王五}]
ds2.user2表數據:[{id=1, name=趙六}]
結論分析

“張三”、”李四”都未插入,”王五”、“趙六”插入成功。

外圍方法和內部的前2個required方法事務管理器都是transactionManager1,所以他們3個在一個事務中執行;而內部的后2個required方法事務管理器是transactionManager2,他們分別在自己的事務中執行,不受外圍方法事務的控制,外圍方法感受到了異常,回滾事務,只會導致內部的前2個required方法回滾

 

4、場景4

Tx2Service中加入代碼
@Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
public void test1() {
    this.ds2User1Service.required("王五");
    this.ds2User2Service.required("趙六");
}
Tx1Service中加入代碼
@Autowired
private Tx2Service tx2Service;
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void test4() {
    this.ds1User1Service.required("張三");
    this.ds1User2Service.required("李四");
    this.tx2Service.test1();
    throw new RuntimeException();
}

方法、事務管理器、事務管理器對應數據源、操作db的jdbctemplate中數據源對應關系。

方法 事務管理器 事務管理器對應數據源 jdbctemplate對應數據源
test4 transactionManager1 datasource1 -
this.ds1User1Service.required transactionManager1 datasource1 datasource1
this.ds1User2Service.required transactionManager1 datasource1 datasource1
this.tx2Service.test1() transactionManager2 datasource2 -
this.ds2User1Service.required transactionManager2 datasource2 datasource2
this.ds2User2Service.required transactionManager2 datasource2 datasource2
Demo7Test中添加測試用例
@Test
public void test4() {
    this.txService1.test4();
}
運行輸出
ds1.user1表數據:[]
ds1.user2表數據:[]
ds2.user1表數據:[{id=1, name=王五}]
ds2.user2表數據:[{id=1, name=趙六}]
結論分析

“張三”、”李四”都未插入,”王五”、“趙六”插入成功。

分析一下過程

1、test4在事務管理器transactionManager1中開啟事務tm1,並將連接放入resourceThreadLocal中(datasource1->conn1)
2、this.ds1User1Service.required("張三")事務管理器是transactionManager1,所以會加入事務tm1中,通過jdbctemplate1插入張三,由於jdbctemplate1.datasource是datasource1,所以會獲取到threadlocal中的conn1來插入數據
3、this.ds1User2Service.required("李四")事務管理器是transactionManager1,所以會加入事務tm1中,通過jdbctemplate1插入張三,由於jdbctemplate1.datasource是datasource1,所以會獲取到threadlocal中的conn1來插入數據
4、執行this.tx2Service.test1(),這個方法事務管理器是transactionManager2,所以會重新開啟一個事務tm2,並將連接放入resourceThreadLocal中(datasource2->conn2)
5、this.ds2User1Service.required("王五")事務管理器是transactionManager2,通過所以會加入事務tm2中,通過jdbctemplate1插入張三,由於jdbctemplate1.datasource是datasource2,所以會獲取到threadlocal中的conn2來插入數據
6、this.ds2User2Service.required("趙六")事務管理器是transactionManager2,所以會加入事務tm2中,通過jdbctemplate1插入張三,由於jdbctemplate1.datasource是datasource2,所以會獲取到threadlocal中的conn2來插入數據
7、tm2提交
8、tm1發現test4拋出異常,tm1執行回滾

5、場景5

Tx2Service中加入代碼
@Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
public void test2() {
    this.ds2User1Service.required("王五");
    this.ds2User2Service.required("趙六");
    throw new RuntimeException();
}
Tx1Service中加入代碼
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void test5() {
    this.ds1User1Service.required("張三");
    this.ds1User2Service.required("李四");
    this.tx2Service.test2();
}

方法、事務管理器、事務管理器對應數據源、操作db的jdbctemplate中數據源對應關系。

方法 事務管理器 事務管理器對應數據源 jdbctemplate對應數據源
test4 transactionManager1 datasource1 -
this.ds1User1Service.required transactionManager1 datasource1 datasource1
this.ds1User2Service.required transactionManager1 datasource1 datasource1
this.tx2Service.test1() transactionManager2 datasource2 -
this.ds2User1Service.required transactionManager2 datasource2 datasource2
this.ds2User2Service.required transactionManager2 datasource2 datasource2
Demo7Test中添加測試用例
@Test
public void test5() {
    this.txService1.test5();
}
運行輸出
ds1.user1表數據:[]
ds1.user2表數據:[]
ds2.user1表數據:[]
ds2.user2表數據:[]
結論分析

4個表都未插入數據

外圍方法test5通過事務管理器transactionManager1開啟了事務tm1,內部方法插入“張三”,“李四”加入了tm1事務,而test2通過事務管理器transactionManager2又開啟了一個事務tm2,test2內部方法插入“王五”,“趙六”加入了tm2事務,test2內部拋出了異常,tm2和tm1都感受到了這個異常,所以2個事務都進行了回滾操作。

案例2

spring配置類

package com.javacode2018.tx.demo8;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
@EnableTransactionManagement //開啟spring事務管理功能
@Configuration //指定當前類是一個spring配置類
@ComponentScan //開啟bean掃描注冊
public class MainConfig8 {
    //定義數據源1,連接數據庫:ds1
    @Bean
    public DataSource dataSource1() {
        org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/ds1?characterEncoding=UTF-8");
        dataSource.setUsername("root");
        dataSource.setPassword("root123");
        dataSource.setInitialSize(5);
        return dataSource;
    }
    //定義一個jdbcTemplate1
    @Bean
    public JdbcTemplate jdbcTemplate1(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    //定義事務管理器transactionManager1
    @Bean
    public PlatformTransactionManager transactionManager1(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
    //定義jdbcTemplate2
    @Bean
    public JdbcTemplate jdbcTemplate2(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
    //定義事務管理器transactionManager2
    @Bean
    public PlatformTransactionManager transactionManager2(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

上面代碼中

  • 定義了1個數據源:dataSource1

  • 2個jdbctemplate:jdbcTemplate1和jdbcTemplate2,他們的datasource都是dataSource1

  • 2個事務管理器:transactionManager1和transactionManager2,他們的datasource都是dataSource1

有同學發現這樣寫是不是很奇怪,不是說一個數據源定義一個事務管理器么,這什么操作?

不急,我們這樣寫,是為了讓你更深入了解其原理。

User2Service

內部的required方法操作db用的是jdbcTemplate2,事務管理器為transactionManager2

package com.javacode2018.tx.demo8;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class User2Service {
    @Autowired
    private JdbcTemplate jdbcTemplate2;
    @Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
    public void required() {
        this.jdbcTemplate2.update("insert into user2(name) VALUES (?)", "李四");
    }
}

User2Service

內部的required方法操作db用的是jdbcTemplate1,事務管理器為transactionManager1,並且會調用user2Service的required方法。

package com.javacode2018.tx.demo8;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Component
public class User1Service {
    @Autowired
    private JdbcTemplate jdbcTemplate1;
    @Autowired
    private User2Service user2Service;
    @Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
    public void required() {
        this.jdbcTemplate1.update("insert into user1(name) VALUES (?)", "張三");
        this.user2Service.required();
        throw new RuntimeException();
    }
}

大家覺得required方法執行完畢之后,會是什么結果?

A:張三未插入、李四插入成功
B:張三、李四均為插入

大家先思考一下,先別看下面的執行結果,可以參考事務管理器的執行過程分析一下結果。

好了,我們上測試用例。

Demo8Test

package com.javacode2018.tx.demo8;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
public class Demo8Test {
    private User1Service user1Service;
    private JdbcTemplate jdbcTemplate1;
    //@Before標注的方法會在任意@Test方法執行之前執行,我們這在里清理一下2庫中4張表的數據
    @Before
    public void before() {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig8.class);
        this.user1Service = context.getBean(User1Service.class);
        this.jdbcTemplate1 = context.getBean("jdbcTemplate1", JdbcTemplate.class);
        jdbcTemplate1.update("truncate table ds1.user1");
        jdbcTemplate1.update("truncate table ds1.user2");
    }
    //@After標注的方法會在任意@Test方法執行完畢之后執行,我們在此處輸出4張表的數據,用來查看測試案例之后,表中的數據清空
    @After
    public void after() {
        System.out.println("ds1.user1表數據:" + this.jdbcTemplate1.queryForList("SELECT * from user1"));
        System.out.println("ds1.user2表數據:" + this.jdbcTemplate1.queryForList("SELECT * from user2"));
    }
    @Test
    public void test1() {
        this.user1Service.required();
    }
}

運行輸出

ds1.user1表數據:[]
ds1.user2表數據:[]

結果是都沒有插入。

結果分析

分析一下執行過程

1、this.user1Service.required();
2、事務攔截器攔截user1Service.required()方法,事務配置信息:(事務管理器:transactionManager1,傳播行為REQUIRED)
3、問一下transactionManager1,當前是否有事務,transactionManager2看了一下,發現沒有,那么重新創建一個事務tm1,通過transactionManager1中的datasource,即datasource1重新獲取一個連接:conn1,然后丟到resourceThreadLocal中(datasource1->conn1)
4、執行this.jdbcTemplate1.update("insert into user1(name) VALUES (?)", "張三"),由於jdbcTemplate1中的datasource是datasource1,所以會從resourceThreadLocal中拿到conn1連接來執行sql
5、執行this.user2Service.required();
6、事務攔截器攔截user1Service.required()方法,事務配置信息:(事務管理器:transactionManager2,傳播行為REQUIRED)
7、問一下transactionManager2,當前是否有事務?大家在回頭看一下事務管理器是如何判斷當前是否有事務的,由於transactionManager2和transactionManager1用到的都是datasource1,所以transactionManager2會發現當前是存在事務的,即tm1
8、執行this.jdbcTemplate2.update("insert into user2(name) VALUES (?)", "李四"),由於jdbcTemplate2中的datasource也是datasource1,所以會從resourceThreadLocal中拿到conn1連接來執行sql
9、最終整個操作過程中只有一個事務tm1,一個連接conn1,通過conn1執行2個插入操作
10、執行throw new RuntimeException();拋出異常
11、tm1感受到了異常,所以會執行回滾操作,最終都插入失敗

總結一下

1、本文介紹了多數據源事務的使用,2個步驟:先為每個數據源定義一個事務管理器,然后在@Transactional中指定具體要使用哪個事務管理器。

2、事務管理器運行過程、事務管理器如何判斷當前是否有事務,這2點非常非常重要,大家再看一下

有問題歡迎留言交流。

案例源碼

git地址:
https://gitee.com/javacode2018/spring-series
本文案例對應源碼:
spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo7
spring-series\lesson-002-tx\src\main\java\com\javacode2018\tx\demo8

來源:http://www.itsoku.com/course/5/128


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM