Spring's Road to God Capítulo 46: ¿Cómo gestiona Spring múltiples transacciones de fuentes de datos?

El contenido de este artículo: a través del principio y una gran cantidad de casos, lo llevaremos a comprender las transacciones de múltiples fuentes de datos de Spring.

Las transacciones son controladas por un administrador de transacciones en Spring, y cada fuente de datos necesita especificar un administrador de transacciones.Si necesitamos operar múltiples bases de datos en nuestro proyecto, necesitamos configurar múltiples fuentes de datos, y necesitamos configurar múltiples dispositivos de administración de datos.

Las transacciones de fuentes de datos múltiples usan 2 pasos

1. Defina un administrador de transacciones para cada fuente de datos

Como en el siguiente código, hay 2 fuentes de datos conectadas a las bases de datos ds1 y ds2 respectivamente, y luego se define 1 administrador de transacciones para cada fuente de datos. En este momento, hay 2 fuentes de datos y 2 administradores de transacciones en el contenedor de primavera.

//数据源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. Especifique el nombre del bean administrador de la transacción.

Al usar @Transaction, debe especificar el nombre del bean del administrador de transacciones a través del valor o el atributo transactionManager de la anotación @Transaction, como:

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

Me gustaría agregar aquí que cuando usamos @Transactional antes, no configuramos el administrador de transacciones a través de value o transactionManager. ¿Por qué ?

Esto se debe a que solo definimos un administrador de transacciones en el contenedor Spring. Cuando Spring inicia una transacción, buscará el administrador de transacciones en el contenedor por tipo de forma predeterminada. Sucede que solo hay un administrador de transacciones en el contenedor, por lo que utilícelo Si hay múltiples Cuando no especifica, Spring no sabe qué administrador de transacciones usar.

El uso de transacciones de múltiples fuentes de datos es tan simple como eso. Veamos el caso, que es la esencia.

proceso del administrador de transacciones

Aquí primero le explicaré el proceso de operación general del administrador de transacciones bajo el comportamiento de propagación REQUERIDO, para facilitar la comprensión del siguiente código de caso.

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 ('李四')");
}

En la transacción de primavera, hay un ThreadLocal de recursos, que se modifica estáticamente y se usa para almacenar recursos compartidos, que se usarán más adelante en el proceso.

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

Veamos el proceso de transacción de la versión simplificada del método 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、清理事务

Como se puede ver en el código anterior: hay dos lugares en todo el proceso que necesitan usar la conexión de la base de datos Objeto de conexión, el primer lugar es: cuando el interceptor de transacciones Spring inicia la transacción, obtendrá una conexión de la fuente de datos, e inicie la transacción a través de esta conexión Envío manual, el segundo lugar es: cuando finalmente se ejecuta la operación sql, también se necesita una conexión. Luego, debe asegurarse de que cuando las dos conexiones deben ser la misma conexión, la ejecución de SQL será controlada por la transacción de primavera, entonces, ¿cómo asegurarse de que estas dos conexiones sean la misma conexión? Se puede ver en el código que la fuente de datos en el administrador de transacciones y la fuente de datos en JdbcTemplate deben ser iguales, luego las dos conexiones finales son el mismo objeto.

Aquí, por cierto, respondo una pregunta de los amigos del grupo: ¿ Qué es una operación de suspensión de transacciones?

Aquí tomamos el comportamiento de propagación de transacciones REQUIRED_NEW como ejemplo. REQUIRED_NEW significa que no importa si hay una transacción en el administrador de transacciones actual, se reabrirá una transacción. Si hay una transacción en el administrador de transacciones actual, la transacción actual será suspendido.

La llamada suspensión, puede entenderla de esta manera: genere una instantánea del sitio de transacciones actual, luego limpie el sitio de transacciones y luego reinicie una nueva transacción. Después de ejecutar la nueva transacción, limpie el sitio de transacciones y luego, de acuerdo con las instantáneas anteriores, restaure las transacciones antiguas .

Volvamos al contenido de este artículo, la gestión de transacciones de fuentes de datos múltiples.

¿Cómo determina el administrador de transacciones si hay una transacción actualmente?

La versión simplificada del proceso es la siguiente:

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

Se puede ver en este código que juzgar si hay una transacción está principalmente relacionado con la fuente de datos y no tiene nada que ver con el administrador de transacciones.Incluso si es un administrador de transacciones diferente, siempre que la fuente de datos del administrador de transacciones sea el mismo, entonces se puede encontrar la transacción actual.

Todos deben comprender el proceso de operación del administrador de transacciones y cómo juzgar si hay una transacción. Si comprende esto, será mucho más fácil comprender los siguientes casos.

A continuación se muestra el caso.

Código fuente del caso

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

Caso 1

Preparar código de caso

1. Preparar base de datos

2 bases de datos: ds1, ds2

2 tablas en cada biblioteca: usuario1, usuario2

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, clase de configuración de resorte

Defina 2 fuentes de datos : dataSource1 y dataSource2, que se utilizan para conectarse a las bases de datos ds1 y ds2 respectivamente

Defina 2 JdbcTemplates : jdbcTemplate1 y jdbcTemplate2, respectivamente asociados con dataSource1 y dataSource2

Las 2 fuentes de datos corresponden a 2 gestores de transacciones : transactionManager1 y transactionManager2, que se utilizan para gestionar las transacciones de las 2 fuentes de datos respectivamente

6 nombres de frijol

fuente de datos Plantilla Jdbc administrador de transacciones
fuente de datos1 jdbcTemplate1 TransaccionManager1
fuente de datos2 jdbcTemplate2 TransaccionManager2

El código fuente es el siguiente:

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

Definamos 4 servicios, que se utilizan para operar 4 tablas en 2 bibliotecas.

3, Ds1Usuario1Servicio

Usado para operar la tabla ds1.user1, tenga en cuenta que el valor de transactionManager en la anotación @Transactional en el siguiente código es 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);
    }
}

Del mismo modo, definamos el servicio que opera las otras 3 tablas

4, Ds1Usuario2Servicio

Utilizado para operar la tabla ds1.user2 , el administrador de transacciones también es 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, Ds2Usuario1Servicio

Se utiliza para operar la tabla ds2.user1 , correspondiente al administrador de transacciones 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, Ds2Usuario2Servicio

Se utiliza para operar la tabla ds2.user2 , correspondiente al administrador de transacciones 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, Servicio 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, Servicio 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. Clase de prueba 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"));
    }
}

verificación de código

1. Escena 1

El método externo y el método interno utilizan el mismo administrador de transacciones, y el comportamiento de propagación es OBLIGATORIO.

Agregar código a Tx1Service
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void test1() {
    
    
    this.ds1User1Service.required("张三");
    this.ds1User2Service.required("李四");
    throw new RuntimeException();
}

Método, administrador de transacciones, origen de datos correspondiente del administrador de transacciones y relación correspondiente del origen de datos en jdbctemplate de la base de datos operativa.

método administrador de transacciones El administrador de transacciones corresponde a la fuente de datos. jdbctemplate corresponde a la fuente de datos
prueba 1 TransaccionManager1 fuente de datos1 -
ds1User1Service.required TransaccionManager1 fuente de datos1 fuente de datos1
this.ds1User2Service.required TransaccionManager1 fuente de datos1 fuente de datos1
Agregar casos de prueba en Demo7Test
@Test
public void test1() {
    
    
    this.txService1.test1();
}
ejecutar salida
ds1.user1表数据:[]
ds1.user2表数据:[]
ds2.user1表数据:[]
ds2.user2表数据:[]
Análisis de conclusiones
resultados de la base de datos Análisis de resultados
"Zhang San" y "Li Si" no están insertados El método periférico y el método interno usan el mismo administrador de transacciones transactionManager1 , y el origen de datos del administrador de transacciones y jdbctemplate son los mismos , el método periférico iniciará la transacción, el método interno se unirá a la transacción del método periférico y el método periférico aparecerá una excepción que hace que la transacción se revierta El método interno Seguido de una reversión.

2. Escena 2

Los métodos externos y los métodos internos utilizan diferentes administradores de transacciones .

Agregar código a Tx1Service
@Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
public void test2() {
    
    
    this.ds1User1Service.required("张三");
    this.ds1User2Service.required("李四");
    throw new RuntimeException();
}

Método, administrador de transacciones, origen de datos correspondiente del administrador de transacciones y relación correspondiente del origen de datos en jdbctemplate de la base de datos operativa.

método administrador de transacciones El administrador de transacciones corresponde a la fuente de datos. jdbctemplate corresponde a la fuente de datos
prueba2 TransaccionManager2 fuente de datos2 -
ds1User1Service.required("Zhang San"); TransaccionManager1 fuente de datos1 fuente de datos1
this.ds1User2Service.required("Li Si"); TransaccionManager1 fuente de datos1 fuente de datos1
Agregar casos de prueba en Demo7Test
@Test
public void test2() {
    
    
    this.txService1.test2();
}
ejecutar salida
ds1.user1表数据:[{id=1, name=张三}]
ds1.user2表数据:[{id=1, name=李四}]
ds2.user1表数据:[]
ds2.user2表数据:[]
Análisis de conclusiones
resultados de la base de datos Análisis de resultados
Se insertan tanto "Zhang San" como "Li Si" El método periférico test2 y los dos métodos internos requeridos no usan el mismo administrador de transacciones . Los dos métodos internos se ejecutan en sus propias transacciones y no están controlados por la transacción del método externo.

3. Escena 3

Agregar código a 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();
}

Método, administrador de transacciones, origen de datos correspondiente del administrador de transacciones y relación correspondiente del origen de datos en jdbctemplate de la base de datos operativa.

método administrador de transacciones El administrador de transacciones corresponde a la fuente de datos. jdbctemplate corresponde a la fuente de datos
prueba3 TransaccionManager1 fuente de datos1 -
this.ds1User1Service.required("Zhang San"); TransaccionManager1 fuente de datos1 fuente de datos1
this.ds1User2Service.required("Li Si"); TransaccionManager1 fuente de datos1 fuente de datos1
this.ds2User1Service.required("Wang Wu"); TransaccionManager2 fuente de datos2 fuente de datos2
this.ds2User2Service.required(“赵六”); TransaccionManager2 fuente de datos2 fuente de datos2
Agregar casos de prueba en Demo7Test
@Test
public void test3() {
    
    
    this.txService1.test3();
}
ejecutar salida
ds1.user1表数据:[]
ds1.user2表数据:[]
ds2.user1表数据:[{id=1, name=王五}]
ds2.user2表数据:[{id=1, name=赵六}]
Análisis de conclusiones

"Zhang San" y "Li Si" no se insertaron, pero "Wang Wu" y "Zhao Liu" se insertaron con éxito.

El método periférico y los dos primeros administradores de transacciones del método requerido son transactionManager1 , por lo que tres de ellos se ejecutan en una transacción, mientras que los dos últimos administradores de transacciones del método requerido interno son transactionManager2 , que se ejecutan en sus propias transacciones respectivamente. transacción del método, el método periférico se siente como una excepción, y revertir la transacción solo hará que se reviertan los primeros dos métodos internos requeridos.

4. Escena 4

Agregar código a Tx2Service
@Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
public void test1() {
    
    
    this.ds2User1Service.required("王五");
    this.ds2User2Service.required("赵六");
}
Agregar código a 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();
}

Método, administrador de transacciones, origen de datos correspondiente del administrador de transacciones y relación correspondiente del origen de datos en jdbctemplate de la base de datos operativa.

método administrador de transacciones El administrador de transacciones corresponde a la fuente de datos. jdbctemplate corresponde a la fuente de datos
prueba4 TransaccionManager1 fuente de datos1 -
este.ds1Usuario1Servicio.requerido TransaccionManager1 fuente de datos1 fuente de datos1
this.ds1User2Service.required TransaccionManager1 fuente de datos1 fuente de datos1
este.tx2Service.test1() TransaccionManager2 fuente de datos2 -
este.ds2User1Service.required TransaccionManager2 fuente de datos2 fuente de datos2
este.ds2User2Service.required TransaccionManager2 fuente de datos2 fuente de datos2
Agregar casos de prueba en Demo7Test
@Test
public void test4() {
    
    
    this.txService1.test4();
}
ejecutar salida
ds1.user1表数据:[]
ds1.user2表数据:[]
ds2.user1表数据:[{id=1, name=王五}]
ds2.user2表数据:[{id=1, name=赵六}]
Análisis de conclusiones

"Zhang San" y "Li Si" no se insertaron, pero "Wang Wu" y "Zhao Liu" se insertaron con éxito.

analizar el proceso

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. Escena 5

Agregar código a Tx2Service
@Transactional(transactionManager = "transactionManager2", propagation = Propagation.REQUIRED)
public void test2() {
    
    
    this.ds2User1Service.required("王五");
    this.ds2User2Service.required("赵六");
    throw new RuntimeException();
}
Agregar código a Tx1Service
@Transactional(transactionManager = "transactionManager1", propagation = Propagation.REQUIRED)
public void test5() {
    
    
    this.ds1User1Service.required("张三");
    this.ds1User2Service.required("李四");
    this.tx2Service.test2();
}

Método, administrador de transacciones, origen de datos correspondiente del administrador de transacciones y relación correspondiente del origen de datos en jdbctemplate de la base de datos operativa.

método administrador de transacciones El administrador de transacciones corresponde a la fuente de datos. jdbctemplate corresponde a la fuente de datos
prueba4 TransaccionManager1 fuente de datos1 -
este.ds1Usuario1Servicio.requerido TransaccionManager1 fuente de datos1 fuente de datos1
this.ds1User2Service.required TransaccionManager1 fuente de datos1 fuente de datos1
este.tx2Service.test1() TransaccionManager2 fuente de datos2 -
este.ds2User1Service.required TransaccionManager2 fuente de datos2 fuente de datos2
este.ds2User2Service.required TransaccionManager2 fuente de datos2 fuente de datos2
Agregar casos de prueba en Demo7Test
@Test
public void test5() {
    
    
    this.txService1.test5();
}
ejecutar salida
ds1.user1表数据:[]
ds1.user2表数据:[]
ds2.user1表数据:[]
ds2.user2表数据:[]
Análisis de conclusiones

Ninguna de las 4 tablas está insertando datos .

外围方法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)]

Supongo que te gusta

Origin blog.csdn.net/china_coding/article/details/130779223
Recomendado
Clasificación