El método de transacción A de Spirng llama al método B, si la transacción no es válida

Springboot abre el método de transacción para llamar al método sin transacción:
提示:上方标题是一个很笼统的场景,详情展开如下,先说结论:

Resumen:
el método A llama al método B:
Escenario 1 Si los métodos A y B están en la misma clase:
si A está anotado con @Transactional, B está anotado con o sin @Transactional y la transacción es válida, entonces AB está en la misma clase. transacción.
Si A no agrega la anotación @Transactional y B agrega la anotación @Transactional, la transacción no es válida. ( 但是如果使用代理类来进行b方法调用,那么B会开启事务,A不会开启事务)

Escenario 2 Si A y B no están en la misma clase:
si A está anotado con @Transactional y B está anotado con o sin @Transactional, la transacción es válida.
Si A no agrega la anotación @Transactional y B agrega la anotación @Transactional, solo B tiene una transacción;
si A no agrega la anotación @Transactional y B no agrega la anotación @Transactional, tanto A como B no tienen transacciones.

O entiéndalo de esta manera
1. Si A agrega la anotación @Transactional, ya sea que esté en una clase o no, ya sea que B agregue la anotación o no, AB está en la misma transacción 2.
Si A no agrega la anotación @Transactional, solo B agrega @Nota transaccional, los métodos AB son de la misma clase y la transacción no es válida (usando la clase proxy, el método B tiene una transacción); AB es de una clase diferente, solo B tiene una transacción; 3. Si A no
agregue la anotación @Transactional, B no agrega la anotación @Transactional, entonces no hay transacción;

Motivo: hay una anotación @Transactional en el método A. Cuando Spring se las arregla, generará una clase de proxy. Cuando se llama al método A, se ejecuta el método en la clase de proxy. El método en la clase de proxy ya incluye el Método B. La llamada se ha convertido en un método. Entonces la transacción es válida.

update方法Nota: La suma de TestUserServiceImpl en el código updateOther方法se denomina método A en el texto completo

Los métodos A y B del escenario 1 están en la misma clase:

Primero creamos un controlador, un servicio y un mapeador nosotros mismos. el código se muestra a continuación

@RestController
@RequestMapping("/testUser")
public class TestShiWuController {
    
    

    @Autowired
    private ITestService testService;
    
    @GetMapping("/update")
    public void update(String param) {
    
    
        testService.update(param);
    }
}
@Service
public class TestUserServiceImpl implements ITestService {
    
    

    @Autowired
    private TestUserMapper testUserMapper;

    @Override
    @Transactional(rollbackFor =Exception.class)
    public int update( String param) {
    
    

        testUserMapper.updateUserById("a",1L);
        b(param);
        c(param);
        return 0;
    }

//    @Transactional(rollbackFor =Exception.class)
    public void b(String s) {
    
    
        TestUser testUser = testUserMapper.selectById(1L);
        System.out.println(testUser);
        testUserMapper.updateUserById("b",2L);
        if (s.equals("b")) {
    
    
            int a = 1/0;
        }
    }

//    @Transactional(rollbackFor =Exception.class)
    public void c(String s) {
    
    

        TestUser testUser = testUserMapper.selectById(2L);
        System.out.println(testUser);
        testUserMapper.updateUserById("c",3L);
        if (s.equals("c")) {
    
    
            int a = 1/0;
        }
    }
}
@Mapper
public interface TestUserMapper extends BaseMapper<TestUser> {
    
    
    @Update("update testUser set name = #{name} where id=#{id}")
    public int updateUserById(String name, Long id );
}

1.1 如果A加@Transactional注解,B不加@Transactional注解:

param=b, el resultado después de la solicitud es el siguiente:
inserte la descripción de la imagen aquí

注意: P: Según la consola de ideas, se puede ver que el método b consulta el registro con id=1, ¿por qué nombre=a en lugar de nombre=1? .
Respuesta: El nombre=a del registro de consulta en este momento se debe a que b está en la transacción de a, 同一事务内数据是可见共享的y el nombre=a no ha caído en la base de datos en este momento. 为了验证此处, puede agregar un Thread.Sleep(10000) al final del método a para dormir durante 10 s, llamar a la interfaz para pasar el parámetro param=a durante estos 10 s, ir a navicat para consultar el registro con id=1 en el base de datos, verifique si es 1, si es 1 prueba que mi anterior es correcto.

Análisis de causa:

Tip: Se puede ver que los métodos b y c se agregan a la transacción principal del método a, es decir, los tres métodos están en la misma transacción:

Análisis: después de llamar a la interfaz con param=b, puede ver intuitivamente, 数据都变成了初始值es decir, retroceder. (En realidad, la transacción tendrá efecto ya sea que se agregue o no el método b con la anotación @Transional, es decir, ambos métodos están en la misma transacción y el blogger lo probó personalmente)

1.2 如果update方法不加@Transactional注解,B加@Transactional注解:

Código, cambie de la siguiente manera:
inserte la descripción de la imagen aquí

param=b, el resultado después de la solicitud es el siguiente:
inserte la descripción de la imagen aquí

注意Pregunta: De acuerdo con la consola de ideas, se puede ver que el método b ha consultado el registro con id=1, 此时name=a和上方1.1标题的name=a有什么区别吗? .
Respuesta: El registro name=a consultado en este momento es porque el cambio del método de actualización ya cayó en la base de datos y no tiene nada que ver con la transacción. 为了验证此处, puede agregar un Thread.Sleep(10000) al final del método a para dormir durante 10 s, llamar a la interfaz para pasar el parámetro param=a durante estos 10 s, ir a navicat para consultar el registro con id=1 en el base de datos, y verifique si el nombre es a, si Name=a dentro de 10s prueba que lo anterior es correcto.

1.3 如果update方法不加@Transactional注解,B加@Transactional注解,并且使用代理类调用B方法:

Código, cambie de la siguiente manera:
inserte la descripción de la imagen aquí


param=b, el resultado después de la solicitud es el siguiente:
inserte la descripción de la imagen aquí
como se muestra en la figura

Conclusión: En esta clase, cuando un método A que no abre una transacción:

1. Llame directamente al método B que ha iniciado la transacción y AB no iniciará la transacción.
2. Pero si el método A usa el objeto proxy para llamar al método B, entonces el método B aún abrirá la transacción, pero el método A aún no tiene transacción.

Resumen del análisis del escenario 1:

Análisis:
Escenario 1.2 Cuando param=b, se pueden observar registros con id=1 e id=2 并未回滚. Se puede observar que en update方法没有开启事务,b方法的事务也失效了。
el Escenario 1.3, cuando param=b, se puede observar el registro con id=1 并未回滚. El registro con id=2 se revirtió. A partir de esto, A方法没有开启事务,B方法的开启了事务。
podemos preguntar: ¿Por qué se da la situación anterior?
Respuesta: Debido a que en esta clase, el método A llama directamente al método B es equivalente a usar this.b() Para obtener detalles, consulte el archivo de compilación de la clase de textura a continuación. El principio de transacción involucrado en @Transitional se basa en AOP, por lo que para que la transacción surta efecto, el método debe llamarse a través del objeto proxy. Esta llamada es equivalente a la llamada del objeto nativo (no mejorada por el proxy de transacción).

Observaciones: en mi entorno de desarrollo springboot 2.3版本,AOP默认代理是Cglib, el objeto proxy puede llamar directamente al método de esta clase, sin la interfaz de implementos, y la conversión forzada de tipos de ServiceImpl también se puede realizar directamente. (Si es un agente jdk, se informará un error) Por supuesto, también puede usar 自己注入自己el método para llamar al método B, pero debe usar el método Implements.


Los métodos A y B del escenario 2 no están en la misma clase:

注意:
TestUserServiceImpl的updateOther 就是A方法
GoodsServiceImpl的updateGoodsById 就是B方法
Primero creamos otra clase de operación por nosotros mismos para observar la situación específica de la transacción (nivel de propagación de la transacción) correspondiente al método de comparación. el código se muestra a continuación

@Service
public class TestUserServiceImpl implements ITestService {
    
    

    @Autowired
    private TestUserMapper testUserMapper;
    @Autowired
    private IGoodsService goodsService;
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateOther(String param) throws InterruptedException {
    
    
        // 该testUser表记录,修改前 name = '1'
        testUserMapper.updateUserById("a", 1L);
        // 该goods表记录,修改前 name = '1'
        goodsService.updateGoodsById("product1", 1L);
        return 1;
    }
}
@Service
public class GoodsServiceImpl implements IGoodsService {
    
    

    @Autowired
    private GoodsMapper goodsMapper;
    @Autowired
    private TestUserMapper testUserMapper;

//    @Transactional(rollbackFor = Exception.class)
    @Override
    public int updateGoodsById(String name, Long id) {
    
    
        TestUser testUser = testUserMapper.selectById(1L);
        System.out.println("GoodsServiceImpl.updateGoodsById()查询的user结果: " + testUser.toString());
        goodsMapper.updateGoodsById(name, id); //          修改前 name = '1'
        int i = 1 / 0;
        return 0;
    }
}
@Mapper
public interface GoodsMapper extends BaseMapper<Goods> {
    
    
    @Update("update goods set name = #{name} where id=#{id}")
    public int updateGoodsById(String name, Long id );
}

2.1 如果A方法加@Transactional注解,b方法不加@Transactional注解:

El resultado después de la solicitud es el siguiente:
inserte la descripción de la imagen aquí

2.1 Análisis de fenómenos: cuando param=b, los datos en mysql no han cambiado, lo que indica que ambos han revertido los datos de transacciones, lo que indica que ambos han iniciado transacciones. Y el cambio de datos del método a se consulta en el método b, lo que indica que los dos son la misma transacción.

Conclusión: cuando se llaman diferentes tipos de métodos (A llama a B), si @Transional se agrega al método A y @Transional no se agrega al método B, entonces el método B se agregará a la transacción del método A.

(Si el método B agrega @Transional, el método AB también está en la misma transacción, porque el nivel de propagación predeterminado de la transacción de Spring)

2.2 如果A方法不加@Transactional注解,B加@Transactional注解:

El resultado después de la solicitud es el siguiente:
inserte la descripción de la imagen aquí

注意Pregunta: De acuerdo con la consola de ideas, se puede ver que el método b ha consultado el registro con id=1, 此时name=a和上方2.1标题的name=a有什么区别吗? .
Respuesta:
2.2 El nombre=a de la consulta del método en el escenario b se debe a que la transacción no se abre en el método A y la actualización de datos se almacena directamente en la base de datos.En este momento, el nombre=a consultado se lee de la base de datos.
2.1 El nombre = a de la consulta del método en el escenario b se debe a que los dos están en la misma transacción, los datos se comparten, parece ser una especie de lectura instantánea (olvidé los detalles, qué es mvcc).

2.2 Análisis de fenómenos: debido a una excepción en el método b, solo la tabla de bienes en mysql tiene una reversión de datos, lo que indica que solo el método B ha iniciado la transacción.

Conclusión: en diferentes clases, A no agrega el método @Transitional para llamar a B para agregar el método @Transitional, A no iniciará la transacción y B iniciará la transacción.


————————————————
Declaración de derechos de autor: el comienzo de este artículo se basa en el siguiente enlace
Enlace original: https://blog.csdn.net/u012279452/article/details/126505417

Supongo que te gusta

Origin blog.csdn.net/lzq2357639195/article/details/129633850
Recomendado
Clasificación