Spring's Road to God 第 46 章: Spring は複数のデータ ソース トランザクションをどのように管理しますか?

この記事の内容:原理と多数の事例を通じて、Spring のマルチデータ ソース トランザクションを理解します。

Springではトランザクションはトランザクションマネージャーによって制御されており、データソースごとにトランザクションマネージャーを指定する必要があるため、プロジェクト内で複数のデータベースを操作する必要がある場合には、複数のデータソースを設定する必要があり、複数のデータ管理装置を設定する必要があります。

複数のデータソーストランザクションは 2 つのステップを使用します

1. データソースごとにトランザクションマネージャーを定義します。

以下のコードのように、データベース ds1 と ds2 にそれぞれ 2 つのデータ ソースが接続されており、各データ ソースに対して 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 アノテーションの値または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 コンテナーにトランザクション マネージャーを 1 つだけ定義しているためです。Spring がトランザクションを開始すると、デフォルトでタイプごとにコンテナー内のトランザクション マネージャーを検索します。コンテナーにはトランザクション マネージャーが 1 つしか存在しないため、複数ある場合 指定しない場合、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 トランザクションには、静的に変更されるリソースの ThreadLocal があり、プロセスの後半で使用される共有リソースを保存するために使用されます。

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

m1 メソッドの簡易バージョンのトランザクション プロセスを見てみましょう。

1TransactionInterceptor拦截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、清理事务

上記のコードからわかるように、プロセス全体でデータベース接続 Connection オブジェクトを使用する必要がある場所が 2 か所あります。最初の場所は、Spring トランザクション インターセプターがトランザクションを開始するときに、データソースから接続を取得します。この接続を介してトランザクションを開始します。手動送信、2 番目は、SQL 操作が最終的に実行されるときにも接続が必要です。次に、2 つの接続が同じ接続である必要がある場合、SQL の実行が Spring トランザクションによって制御されるようにする必要があります。では、これら 2 つの接続が同じ接続であることを確認するにはどうすればよいでしょうか。コードから、トランザクション マネージャーのデータソースと JdbcTemplate のデータソースは同じである必要があり、最後の 2 つの接続は同じオブジェクトであることがわかります。

ちなみに、グループの友人からの質問に答えます。「トランザクション一時停止操作とは何ですか?」

ここでは、トランザクション伝播動作 REQUIRED_NEW を例として取り上げます。REQUIRED_NEW は、現在のトランザクション マネージャーにトランザクションがあるかどうかに関係なく、トランザクションが再度開かれることを意味します。現在のトランザクション マネージャーにトランザクションがある場合、現在のトランザクションは一時停止。

いわゆる一時停止は、次のように理解できます:現在のトランザクション サイトのスナップショットを生成し、トランザクション サイトをクリーンアップして、新しいトランザクションを再開します。新しいトランザクションが実行された後、トランザクション サイトをクリーンアップし、次に、以前のスナップショットに従って古いトランザクションを復元します

この記事の内容であるマルチデータソースのトランザクション管理に戻りましょう。

トランザクション マネージャーは、現在トランザクションが存在するかどうかをどのように判断しますか?

プロセスの簡略版は次のとおりです。

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

このコードからわかるように、トランザクションの有無の判断は主にデータソースに関係しており、トランザクションマネージャーとは関係がありません。同じであれば、現在のトランザクションを見つけることができます。

誰もがトランザクションマネージャの動作プロセスとトランザクションの有無を判断する方法を理解する必要があり、これを理解すると、次のようなケースを理解しやすくなります。

以下はその場合です。

ケースのソースコード

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.データベースの準備

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、スプリング構成クラス

2 つのデータ ソースを定義します: dataSource1 と dataSource2。それぞれデータベース ds1 と ds2 に接続するために使用されます。

2 つの JdbcTemplate を定義します: jdbcTemplate1 と jdbcTemplate2 は、それぞれ dataSource1 と dataSource2 に関連付けられます

2 つのデータ ソースは、2 つのトランザクション マネージャー (transactionManager1 とtransactionManager2) に対応し、それぞれ 2 つのデータ ソースのトランザクションを管理するために使用されます。

6つの豆の名前

情報元 Jdbcテンプレート トランザクションマネージャー
データソース1 jdbcテンプレート1 トランザクションマネージャー1
データソース2 jdbcテンプレート2 トランザクションマネージャー2

ソースコードは次のとおりです。

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);
    }
}

2 つのライブラリ内の 4 つのテーブルを操作するために使用される 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つのテーブルを操作するサービスを定義しましょう

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、Tx1サービス

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、Tx2サービス

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

外部メソッドと内部メソッドは同じトランザクション マネージャーを使用し、伝播動作は必須です。

Tx1Service にコードを追加
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void test1() {
    
    
    this.ds1User1Service.required("张三");
    this.ds1User2Service.required("李四");
    throw new RuntimeException();
}

運用DBのjdbctemplateにおけるメソッド、トランザクションマネージャ、トランザクションマネージャの対応データソース、データソースの対応関係。

方法 トランザクションマネージャー トランザクションマネージャーはデータソースに対応します jdbctemplateはデータソースに対応します
テスト1 トランザクションマネージャー1 データソース1 -
ds1User1Service.必須 トランザクションマネージャー1 データソース1 データソース1
this.ds1User2Service.required トランザクションマネージャー1 データソース1 データソース1
Demo7Test にテスト ケースを追加する
@Test
public void test1() {
    
    
    this.txService1.test1();
}
実行出力
ds1.user1表数据:[]
ds1.user2表数据:[]
ds2.user1表数据:[]
ds2.user2表数据:[]
結論分析
データベースの結果 結果分析
「張三」と「李斯」は挿入されていません ペリフェラル メソッドと内部メソッドは同じトランザクション マネージャーtransactionManager1を使用し、トランザクション マネージャーのデータソースと jdbctemplate は同じです。ペリフェラル メソッドがトランザクションを開始し、内部メソッドがペリフェラル メソッドのトランザクションに参加し、ペリフェラル メソッドがポップします。トランザクションのロールバックを引き起こす例外を発生させます。内部メソッドの後にロールバックが続きます。

2. シーン2

外部メソッドと内部メソッドでは、異なるトランザクション マネージャーが使用されます

Tx1Service にコードを追加
@Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
public void test2() {
    
    
    this.ds1User1Service.required("张三");
    this.ds1User2Service.required("李四");
    throw new RuntimeException();
}

運用DBのjdbctemplateにおけるメソッド、トランザクションマネージャ、トランザクションマネージャの対応データソース、データソースの対応関係。

方法 トランザクションマネージャー トランザクションマネージャーはデータソースに対応します jdbctemplateはデータソースに対応します
テスト2 トランザクションマネージャー2 データソース2 -
ds1User1Service.required("張三"); トランザクションマネージャー1 データソース1 データソース1
this.ds1User2Service.required("リー・シ"); トランザクションマネージャー1 データソース1 データソース1
Demo7Test にテスト ケースを追加する
@Test
public void test2() {
    
    
    this.txService1.test2();
}
実行出力
ds1.user1表数据:[{id=1, name=张三}]
ds1.user2表数据:[{id=1, name=李四}]
ds2.user1表数据:[]
ds2.user2表数据:[]
結論分析
データベースの結果 結果分析
「張三」と「李斯」の両方が挿入されています 周辺メソッド test2 と内部の 2 つの必須メソッドは、同じトランザクション マネージャーを使用しません。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はデータソースに対応します
テスト3 トランザクションマネージャー1 データソース1 -
this.ds1User1Service.required("張三"); トランザクションマネージャー1 データソース1 データソース1
this.ds1User2Service.required("リー・シ"); トランザクションマネージャー1 データソース1 データソース1
this.ds2User1Service.required("王呉"); トランザクションマネージャー2 データソース2 データソース2
this.ds2User2Service.required(“赵六”); トランザクションマネージャー2 データソース2 データソース2
Demo7Test にテスト ケースを追加する
@Test
public void test3() {
    
    
    this.txService1.test3();
}
実行出力
ds1.user1表数据:[]
ds1.user2表数据:[]
ds2.user1表数据:[{id=1, name=王五}]
ds2.user2表数据:[{id=1, name=赵六}]
結論分析

「Zhang San」と「Li Si」は挿入されませんでしたが、「Wang Wu」と「Zhao Liu」は正常に挿入されました。

ペリフェラル メソッドと最初の 2 つの必須メソッド トランザクション マネージャーはtransactionManager1であるため、そのうちの 3 つは 1 つのトランザクションで実行されますが、最後の 2 つの内部必須メソッド トランザクション マネージャーはtransactionManager2であり、それぞれ独自のトランザクションで実行されます。メソッド トランザクションの場合、周辺メソッドは例外を認識し、トランザクションをロールバックすると、最初の 2 つの内部必須メソッドのみがロールバックされます。

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はデータソースに対応します
テスト4 トランザクションマネージャー1 データソース1 -
this.ds1User1Service.required トランザクションマネージャー1 データソース1 データソース1
this.ds1User2Service.required トランザクションマネージャー1 データソース1 データソース1
this.tx2Service.test1() トランザクションマネージャー2 データソース2 -
this.ds2User1Service.required トランザクションマネージャー2 データソース2 データソース2
this.ds2User2Service.required トランザクションマネージャー2 データソース2 データソース2
Demo7Test にテスト ケースを追加する
@Test
public void test4() {
    
    
    this.txService1.test4();
}
実行出力
ds1.user1表数据:[]
ds1.user2表数据:[]
ds2.user1表数据:[{id=1, name=王五}]
ds2.user2表数据:[{id=1, name=赵六}]
結論分析

「Zhang San」と「Li Si」は挿入されませんでしたが、「Wang Wu」と「Zhao Liu」は正常に挿入されました。

プロセスを分析する

1、test4在事务管理器transactionManager1中开启事务tm1,并将连接放入resourceThreadLocal中(datasource1->conn1)
2this.ds1User1Service.required("张三")事务管理器是transactionManager1,所以会加入事务tm1中,通过jdbctemplate1插入张三,由于jdbctemplate1.datasource是datasource1,所以会获取到threadlocal中的conn1来插入数据
3this.ds1User2Service.required("李四")事务管理器是transactionManager1,所以会加入事务tm1中,通过jdbctemplate1插入张三,由于jdbctemplate1.datasource是datasource1,所以会获取到threadlocal中的conn1来插入数据
4、执行this.tx2Service.test1(),这个方法事务管理器是transactionManager2,所以会重新开启一个事务tm2,并将连接放入resourceThreadLocal中(datasource2->conn2)
5this.ds2User1Service.required("王五")事务管理器是transactionManager2,通过所以会加入事务tm2中,通过jdbctemplate1插入张三,由于jdbctemplate1.datasource是datasource2,所以会获取到threadlocal中的conn2来插入数据
6this.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はデータソースに対応します
テスト4 トランザクションマネージャー1 データソース1 -
this.ds1User1Service.required トランザクションマネージャー1 データソース1 データソース1
this.ds1User2Service.required トランザクションマネージャー1 データソース1 データソース1
this.tx2Service.test1() トランザクションマネージャー2 データソース2 -
this.ds2User1Service.required トランザクションマネージャー2 データソース2 データソース2
this.ds2User2Service.required トランザクションマネージャー2 データソース2 データソース2
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

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**

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-39QQ50jv-1684551870786)(https://itsoku.oss-cn-hangzhou.aliyuncs.com/itsoku/blog/article/1/2883e86e-3eff-404a-8943-0066e5e2b454.png)]

おすすめ

転載: blog.csdn.net/china_coding/article/details/130779223