Comprensión y prueba simple del mecanismo de propagación de transacciones en Spring

Tabla de contenido

I. Introducción

En segundo lugar, el comportamiento de propagación de transacciones en el marco Spring.

Tres, prueba de comportamiento de propagación de transacciones

Propagación.REQUERIDA

Propagación.SOPORTES

Propagación.OBLIGATORIO

Propagación.REQUIRES_NUEVO

Propagación.NO_SUPPORTED

Propagación.NUNCA

Propagación.ANIDADO

4. Resumen de escenarios de falla de transacción

Cinco escenarios de uso del mecanismo de propagación de transacciones


 

I. Introducción

Creo que todos pueden pensar en transacciones en la base de datos y sus correspondientes características ACID cuando hablan de transacciones. Pero, de hecho, no es solo en la base de datos donde hay una transacción, su función central también tiene su propia función única en el marco de Spring. Tal vez algunos amigos hayan escuchado sobre las transacciones de Spring en entrevistas o estudios, pero realmente no entienden sus funciones específicas y escenarios de uso, por lo que cada vez que usan transacciones de Spring, agregan directamente @Transactional( rollbackFor = Exception.class), ya no importa después de eso, no estudié en detalle el comportamiento de propagación de la transacción, lo que dificultó su uso al usarlo. Así que hoy haré un resumen simple del aprendizaje y la comprensión del mecanismo de transacción de Spring.

Consejos: Las transacciones en la base de datos deben cumplir 4 condiciones (ACID): (Atomicidad, o indivisibilidad), (Consistencia), (Isolación原子性 , también conocida 一致性como independencia ) , ( Durabilidad ) .隔离性持久性

  • Atomicidad: todas las operaciones en una transacción se completan o no se completan, y no terminarán en un cierto enlace en el medio. Si ocurre un error durante la ejecución de la transacción, se retrotraerá (Rollback) al estado anterior al inicio de la transacción, como si la transacción nunca se hubiera ejecutado.

  • Coherencia: la integridad de la base de datos no se viola antes de que comience la transacción y después de que finalice. Esto significa que los datos escritos deben cumplir completamente con todas las reglas preestablecidas, incluida la precisión de los datos, la serialidad y la base de datos posterior puede completar espontáneamente el trabajo programado.

  • Aislamiento: la capacidad de la base de datos para permitir que múltiples transacciones simultáneas lean, escriban y modifiquen sus datos al mismo tiempo. El aislamiento puede evitar la inconsistencia de los datos causada por la ejecución cruzada cuando se ejecutan múltiples transacciones al mismo tiempo. El aislamiento de transacciones se divide en diferentes niveles, que incluyen lectura no confirmada (Lectura no confirmada), lectura confirmada (lectura confirmada), lectura repetible (lectura repetible) y serie (Serializable).

  • Persistencia: Después de que finaliza el procesamiento de la transacción, la modificación de los datos es permanente, incluso si el sistema falla, no se perderá.

En segundo lugar, el comportamiento de propagación de transacciones en el marco Spring.

En el marco Spring, el mecanismo de propagación de transacciones define cómo tratar las transacciones existentes al llamar a métodos con funciones de transacción.

Spring proporciona una variedad de comportamientos de propagación de transacciones, cada uno de los cuales es aplicable a diferentes escenarios de desarrollo y requisitos comerciales. Los siguientes son comportamientos comunes de propagación de transacciones y sus descripciones:

Tipo de comportamiento de propagación de transacciones explicar
Requied(默认) Si no hay una transacción actual, se crea una nueva y se usa durante la ejecución del método. Si existe una transacción actualmente, el método se une a ella y se convierte en parte de ella. Este es el comportamiento de propagación de transacciones más utilizado, utilizado para garantizar que el método se ejecute en una transacción. Si no hay transacción, cree una nueva transacción; si hay transacción, únase a la transacción.
Supports Si actualmente existe una transacción, el método se ejecutará dentro de esa transacción. Si no hay una transacción actual, el método se ejecutará de forma no transaccional. Este comportamiento de propagación de transacciones es adecuado para situaciones en las que no es obligatorio que los métodos se ejecuten dentro de una transacción.
Mandatory Si existe una transacción actualmente, el método se ejecutará dentro de esa transacción. Se lanza una excepción si no hay una transacción actual. Este comportamiento de propagación de transacciones se aplica al caso en el que se requiere que un método se ejecute en una transacción, lo que indica que el método debe ejecutarse en una transacción.
Requires_New Si actualmente existe una transacción, suspenda la transacción actual y cree una nueva. El método se ejecuta en una nueva transacción. Si no hay una transacción actual, el método se ejecutará en una nueva transacción. Este comportamiento de propagación de transacciones es adecuado para los casos en los que se requiere que los métodos se ejecuten dentro de una nueva transacción. Independientemente de si hay una transacción actual, el método se ejecutará en una nueva transacción y la transacción actual se suspenderá.
Not_Supported El método se ejecutará de forma no transaccional. Si existe una transacción actualmente, se suspende. Este comportamiento de propagación de transacciones es adecuado para situaciones que requieren que los métodos no se ejecuten dentro de una transacción. Independientemente de si existe actualmente una transacción, el método se ejecuta de manera no transaccional y suspende la transacción actual.
Never El método se ejecutará de forma no transaccional. Se lanza una excepción si existe una transacción actualmente. Este comportamiento de propagación de transacciones es adecuado para situaciones en las que se requiere que los métodos nunca se ejecuten dentro de una transacción. Se lanza una excepción si existe una transacción actualmente.
Nested Si actualmente existe una transacción, el método se ejecutará en una transacción anidada. Si no hay una transacción actual, se crea y utiliza una nueva transacción durante la ejecución del método. Las transacciones anidadas son parte de la transacción externa y se pueden revertir a un punto de guardado del estado de la transacción. Este comportamiento de propagación de transacciones es adecuado para situaciones en las que se requiere una reversión parcial y las transacciones externas se pueden revertir y afectar el estado de las transacciones anidadas internas.

Los 7 mecanismos de propagación anteriores se pueden dividir en las siguientes 3 categorías según la dimensión de "si se admite la transacción actual":

dc5f5aad6c7b4f76a4248d83f5da8fac.png

En el proyecto, puede @Transactionaliniciar la transacción utilizando esta anotación

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
​
    @AliasFor("value")
    String transactionManager() default "";
​
    Propagation propagation() default Propagation.REQUIRED;
​
    Isolation isolation() default Isolation.DEFAULT;
​
    int timeout() default -1;
​
    boolean readOnly() default false;
​
    Class<? extends Throwable>[] rollbackFor() default {};
​
    String[] rollbackForClassName() default {};
​
    Class<? extends Throwable>[] noRollbackFor() default {};
​
    String[] noRollbackForClassName() default {};
}

En el código fuente de esta anotación, puede ver que actualmente es el archivo REQUIRED.

Si necesita cambiar el tipo de propagación de transacción utilizada, puede usar Propagationesta clase de enumeración para marcarla en forma de anotaciones:

package org.springframework.transaction.annotation;
​
public enum Propagation {
    REQUIRED(0),
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
​
    private final int value;
​
    private Propagation(int value) {
        this.value = value;
    }
​
    public int value() {
        return this.value;
    }
}

Los beneficios del mecanismo de propagación de transacciones son:

  • Simplifique la lógica del código: el mecanismo de propagación de transacciones permite a los desarrolladores administrar varias llamadas a métodos como una transacción si es necesario, sin tener que manejar manualmente operaciones como el inicio y la confirmación de transacciones.

  • Proporcione aislamiento de transacciones: al controlar el comportamiento de propagación de las transacciones, puede asegurarse de que los métodos ejecutados en una transacción puedan compartir el mismo contexto de transacción, manteniendo así el aislamiento y la consistencia de la transacción.

  • Compatibilidad con transacciones anidadas: la opción NESTED en el mecanismo de propagación de transacciones permite que los métodos se ejecuten en transacciones anidadas, proporciona un control de transacciones de nivel superior y admite la reversión parcial.

¿Cuál es exactamente la función del mecanismo de transmisión?

El punto de vista personal del autor es que las transacciones son una característica de las bases de datos y, para Spring只是封装这个特性nuestra comodidad, la ejecución final en realidad se completa en la base de datos, pero para las bases de datos, las transacciones son únicas y no hay tantos escenarios comerciales, pero para Spring , enfrentará una variedad de necesidades comerciales, por lo que debe tener un conjunto que pueda controlar las transacciones desde el nivel del código para satisfacer las necesidades de nuestro escenario, por lo que existe un mecanismo de propagación.

Dado que se trata de "propagación de transacciones", el número de transacciones debe ser de dos o más. El mecanismo de propagación de transacciones de Spring nació para especificar el comportamiento de múltiples transacciones durante el proceso de propagación. Por ejemplo, el método A inicia una transacción y llama al método B que inicia la transacción durante la ejecución, entonces, ¿debería agregarse la transacción del método B a la transacción de A? ¿O las dos transacciones se ejecutan sin afectarse entre sí, o la transacción B está anidada en la transacción A para su ejecución? Entonces, en este momento, se necesita un mecanismo para especificar y restringir el comportamiento de estas dos transacciones, que es el problema resuelto por el mecanismo de propagación de transacciones de Spring.

En términos sencillos, cuando nuestro proyecto es relativamente simple y la lógica comercial no es complicada, cuando un método comercial procesa una operación de datos, como simplemente verificar datos, eliminar datos, etc., y procesar una sola operación de capa de datos dentro de un método, entonces esto no se usa Las transacciones de Spring son necesarias, porque una sola operación de datos se considera una transacción de base de datos básica en la base de datos, ya sea exitosa o fallida. Pero si el procesamiento comercial es complicado, es un asunto diferente. Por ejemplo, un método de procesamiento de pedidos comerciales incluye dos pasos de deducción después de que se completa el pago del pedido y el envío del pedido después de que se completa la deducción, al igual que Taobao. , habrá pasos como la información del pedido cuando se reciben las mercancías. En este momento, si no hay una intervención en la transacción para garantizar que la deducción y la información del pedido sean consistentes con la nueva, es posible que pague, pero la operación de la base de datos en medio de la ejecución del método (dos pasos) puede ser anormal. , y el estado del pedido no se actualiza como resultado. ¿No acabas de interrumpirlo? Por lo tanto, cuando se trata de lógica comercial compleja, es absolutamente necesario aprender a agregar procesamiento de transacciones para garantizar la consistencia de la ejecución del método comercial.

¿Cómo se implementa la transacción Spring?

Debido a que Spring en sí mismo no tiene transacciones, solo la base de datos tendrá transacciones, y Spring的事务是借助AOP,通过动态代理的方式cuando queremos operar la base de datos, Spring en realidad expande la función a través del proxy dinámico y abre la base de datos a través del cliente de la base de datos antes de que nuestro código opere la transacción de la base de datos, si no hay información de excepción después de que se ejecuta el código o no hay información de excepción para ser capturada por Spring, luego envíe la transacción a través del programa cliente de la base de datos.Si hay información de excepción o hay información de excepción para ser capturada por Spring, entonces regrese para hacer rodar las transacciones, a fin de lograr el propósito de controlar las transacciones de la base de datos. A continuación se adjunta un diagrama de flujo de la creación de una transacción en Spring para que todos lo entiendan. Los socios interesados ​​también pueden verlo a través del código fuente, que no se detalla aquí:

b5d3c676e2e54deca0d6e1067055bf9f.png

La siguiente es una comprensión de uso simple para cada comportamiento de propagación diferente.

Tres, prueba de comportamiento de propagación de transacciones

Por lo tanto, el número de transacciones debe ser dos o más, así que primero prepare dos métodos para probar en el proyecto.

@Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
/**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取结果-》{}",result);
        log.info("更新状态");
                try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    /**
     * 被调用方法
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

En este momento insertData, como el método que llama, updateSatuscomo el método llamado por insertData. De esta forma, después de agregar diferentes mecanismos de propagación de transacciones para transacciones, pruebe el impacto de diferentes comportamientos de propagación de transacciones en la ejecución del método. Echemos un vistazo a lo que sucede si no se une a la transacción:

Agregue una identificación que no existe en la base de datos de administración a los datos de prueba y pruebe.

@Autowired
    private TestUserServiceImpl testUserService;
​
    private static final TestUser tu =new TestUser();
    static {
        tu.setId(103).setName("张三");
    }
    @Test
     void requiredResult(){
        System.out.println(testUserService.insertData(tu));
    }

Cuando el método se ejecuta en este momento insertData, porque primero llama al método de actualización del estado para consultar la base de datos y actualizarla, pero la base de datos no tiene datos con una identificación de 103, la actualización falla y se lanza una excepción, por lo que qué pasará con el resultado final de la base de datos. . .

JDBC Connection [HikariProxyConnection@1266035080 wrapping com.mysql.cj.jdbc.ConnectionImpl@24f2608b] will not be managed by Spring
==>  Preparing: INSERT INTO test_user ( id, name, createTime ) VALUES ( ?, ?, ? )
==> Parameters: 103(Integer), 张三(String), 2023-07-06 20:01:27.666(Timestamp)
<==    Updates: 1
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@51172948]
2023-07-06 20:01:28.628  INFO 7752 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 获取数据插入结果->数据插入成功
2023-07-06 20:01:28.628  INFO 7752 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新状态
2023-07-06 20:01:28.628  INFO 7752 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新对象id为-》103的状态
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@736f8837] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@1233162869 wrapping com.mysql.cj.jdbc.ConnectionImpl@24f2608b] will not be managed by Spring
==>  Preparing: select id,username,pwd,avatar,status,token from admin where id= ?
==> Parameters: 103(Integer)
<==      Total: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@736f8837]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2c3b0cc8] was not registered for synchronization because synchronization is not active
JDBC Connection [HikariProxyConnection@43924520 wrapping com.mysql.cj.jdbc.ConnectionImpl@24f2608b] will not be managed by Spring
==>  Preparing: UPDATE admin SET status=? WHERE (id = ?)
==> Parameters: 1(Integer), 103(Integer)
<==    Updates: 0
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2c3b0cc8]
2023-07-06 20:01:28.693  INFO 7752 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新失败!
​
模拟异常
MyException(code=501)

Se puede ver que después de que los datos se insertan con éxito, si hay una excepción en la operación subsiguiente, la operación de la base de datos no se revertirá. Esto es como deducir dinero sin la información de finalización del pago del pedido. Esto definitivamente no es posible en los negocios diarios. desarrollo, por lo que tenemos que utilizar el negocio para manejar.

Propagación.REQUERIDA

Esto significa que si no hay transacción, agregue la transacción y, si la hay, ejecute de acuerdo con la transacción. Como método de propagación predeterminado de transacciones, este también es el más común. Aquí, podemos considerar la relación de procesamiento de transacciones cuando varios métodos tienen una relación anidada basada en el comportamiento de propagación predeterminado de las cosas.

Cuando la relación de anidamiento de este método aparezca en la clase, ¿se revertirá 被调用的方法加入事务处理? Es decir, un método con cosas está anidado en un método sin transacciones.

/**
     * 被调用方法,加入Propagation.REQUIRED事务处理
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

Luego insertDatallame al método nuevamente probando:

@Autowired
    private TestUserServiceImpl testUserService;
​
    private static final TestUser tu =new TestUser();
    static {
        tu.setId(104).setName("张三");
    }
    @Test
     void requiredResult(){
        System.out.println("无事务处理的执行结果:"+testUserService.insertData(tu));
    }

Los datos con el ID 104 aún se insertan en la base de datos y no se ha implementado la reversión. Esto se debe a que después de que la clase que no se ha unido a la transacción se envía por proxy, todavía se llama al método original de updateStatus en la clase de proxy.

Por el contrario, ¿y si lo es 有事物的方法里调用一个没有事务的方法?

/**
     * insertData作为方法调用者,加入事务
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
                try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    /**
     * 被调用方法,加入Propagation.REQUIRED事务处理
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    //@Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

En este punto, pruebe e inserte un dato con una identificación de 105, aunque el método aún no se ejecuta, esto es seguro, después de todo, hay una excepción.

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
JDBC Connection [HikariProxyConnection@1120514542 wrapping com.mysql.cj.jdbc.ConnectionImpl@217c6a1e] will be managed by Spring
==>  Preparing: INSERT INTO test_user ( id, name, createTime ) VALUES ( ?, ?, ? )
==> Parameters: 105(Integer), 张三(String), 2023-07-06 21:01:31.562(Timestamp)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 21:01:31.675  INFO 10940 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 获取数据插入结果->数据插入成功
2023-07-06 21:01:31.675  INFO 10940 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新状态
2023-07-06 21:01:31.675  INFO 10940 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新对象id为-》105的状态
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d] from current transaction
==>  Preparing: select id,username,pwd,avatar,status,token from admin where id= ?
==> Parameters: 105(Integer)
<==      Total: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d] from current transaction
==>  Preparing: UPDATE admin SET status=? WHERE (id = ?)
==> Parameters: 1(Integer), 105(Integer)
<==    Updates: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 21:01:31.768  INFO 10940 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新失败!
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
​
模拟异常
MyException(code=501)
    at com.yy.testUser.service.Impl.TestUserServiceImpl.updateStatus(TestUserServiceImpl.java:76)
    at com.yy.testUser.service.Impl.TestUserServiceImpl.insertData(TestUserServiceImpl.java:60)

Sin embargo, la operación de la base de datos en este método se ha revertido. ¡Porque la base de datos no ha insertado los datos con id 105!

 e1692656109841b5ae72d30a9cfb2b7f.png

La razón es que cuando se utiliza el proxy, el método sin transacciones se envuelve directamente en el método proxy con transacciones, por lo que el efecto de la transacción se realiza y se revertirá si falla.

Similar al siguiente formulario:

//被代理后的方法
proxyInsertData(){
        //开启事务
        原始类.insertData(){
            原始类.updateStatus();
        }
    }

De esta forma, aunque se produzca una excepción en el método ejecutado, no se te cobrará si falla la ejecución del método de negocio. Entonces, si la transacción está anidada en la misma clase, el resultado depende de las características de propagación del método externo. Y cuando ocurre una excepción en el método interno, incluso si el método externo detecta y maneja la excepción, los datos aún se revertirán. Esta es también la función de Requerido: Propagation.REQUIREDcuando hay una transacción en el método externo y se usa la modificación, todos los métodos internos no crearán una nueva transacción y se ejecutarán directamente en la transacción actual (siempre que no se especifiquen otras características de propagación únicas, seguir viendo este punto más adelante )

Propagación.SOPORTES

Si existe una transacción, el método se ejecutará dentro de una transacción; si no existe ninguna transacción, el método se ejecutará de forma no transaccional. Es decir, sigue el método externo, cuando el método externo especifica este comportamiento de propagación, es equivalente a ninguna transacción.

/**
     * insertData作为方法调用者,加入SUPPORTS
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.SUPPORTS,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
                try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    
    /**
     * 被调用方法,加入Propagation.REQUIRED事务处理
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

En este punto es como la primera prueba, por dentro updateStatus方法有Propagation.REQUIRED修饰, pero por fuera insertData没有事务. Por lo tanto, al insertar datos, incluso si luego hay una situación anormal, el resultado de la operación de ejecución no activará el mecanismo de reversión de la transacción.

Propagación.OBLIGATORIO

Obligatorio significa que el método modificado debe ejecutarse en una transacción. Es fácil entender que debe usarse en el método de transacción, de lo contrario, se lanzará una excepción directamente.

/**
     * insertData作为方法调用者,MANDATORY
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.MANDATORY,rollbackFor = MyException.class)
    public String insertDataMANDATORY(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
                try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    
    /**
     * 被调用方法,加入Propagation.REQUIRED事务处理
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

Ejecutar la prueba de esta manera arroja una excepción directamente:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
​
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:362)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:574)
    ……

Entonces, si hay algo afuera, estará bien si el método interno usa Obligatorio para difundir el comportamiento. Cuando se ejecuta el método interno, se agregará a la transacción del método externo y se ejecutará de acuerdo con las reglas de la transacción.

Propagación.REQUIRES_NUEVO

Indica que el método decorado debe ejecutarse en su propia transacción. Se iniciará una nueva transacción. Si la persona que llama tiene una transacción actual, la transacción actual se suspenderá durante la ejecución de este método. Es decir, el comportamiento de propagación de la llamada interna es que este método primero anulará la transacción del método externo y luego creará su propio método de ejecución de transacciones de forma independiente, aislado entre sí, y finalmente irá a la transacción externa:

/**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
            updateStatus(testUser.getId());
        }catch (Exception e) {
            e.printStackTrace();
        } 
        return result;
    }
​
    
    /**
     * 被调用方法,加入Propagation.REQUIRES_NEW
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        if(adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id))>0){
            log.info("更新成功!" );
        }else {
            log.info("更新失败!" );
            throw new MyException(501,"模拟异常");
        }
    }

Vuelva a probar, inserte un dato con una identificación de 107 y lance una excepción cuando falle la actualización del estado:

Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
JDBC Connection [HikariProxyConnection@1120514542 wrapping com.mysql.cj.jdbc.ConnectionImpl@217c6a1e] will be managed by Spring
==>  Preparing: INSERT INTO test_user ( id, name, createTime ) VALUES ( ?, ?, ? )
==> Parameters: 107(Integer), 第107号测试(String), 2023-07-06 22:52:50.194(Timestamp)
<==    Updates: 1
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 22:52:50.279  INFO 17768 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 获取数据插入结果->数据插入成功
[org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 22:52:50.283  INFO 17768 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新状态
2023-07-06 22:52:50.283  INFO 17768 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新对象id为-》107的状态
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d] from current transaction
==>  Preparing: select id,username,pwd,avatar,status,token from admin where id= ?
==> Parameters: 107(Integer)
<==      Total: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
Fetched SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d] from current transaction
==>  Preparing: UPDATE admin SET status=? WHERE (id = ?)
==> Parameters: 1(Integer), 107(Integer)
<==    Updates: 0
Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@1fb30e5d]
2023-07-06 22:52:50.357  INFO 17768 --- [           main] c.y.t.service.Impl.TestUserServiceImpl   : 更新失败!
MyException(code=501)
    at com.yy.testUser.service.Impl.TestUserServiceImpl.updateStatus(TestUserServiceImpl.java:83)
    at com.yy.testUser.service.Impl.TestUserServiceImpl.insertData(TestUserServiceImpl.java:62)

Aquí es obvio que la base de datos inserta los datos de prueba con la identificación 107, pero la actualización falla porque no existe tal identificación durante la actualización, pero las dos transacciones se ejecutan de forma independiente y están aisladas entre sí, por lo que la operación de inserción es normalmente, y el mecanismo de reversión se desencadena por la excepción. Aquí está el problema mencionado anteriormente usando Required : 如果内部的方法有其他事务传播行为,那么外部方法即使标名使用Required,内部也会执行它自己的传播行为.

Propagación.NO_SUPPORTED

Indica que el método modificado no se ejecutará en una transacción.Si el método llamado ya tiene una transacción, también se suspenderá y se suspenderá la transacción actual. Antes de probar este comportamiento de propagación de transacciones, pensemos en ello 同一个类中出现事务嵌套和不同类中的事务嵌套是否是一致的问题. Lo anterior también mencionó que cuando el método externo tiene una transacción y utiliza Propagation.REQUIREDla modificación, todos los métodos internos no crearán una nueva transacción y se ejecutarán directamente en la transacción actual , pero nuestros dos métodos están en la misma clase durante la prueba, por lo que el punto de la vista es realmente limitada. Entonces, esta vez, veámoslo con la ayuda del comportamiento de propagación Not_Supported.

Cree una nueva clase externa AdminServiceImply agregue un método de prueba con una excepción, y especifique su comportamiento de propagación de transacciones como Not_Supported:

@Service
@Slf4j
public class  extends ServiceImpl<AdminDao, Admin>
    implements AdminService{
  @Resource
  private AdminDao adminDao;
  @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        Admin status = Admin.builder().id(id).status((byte) 1).build();
        //更新信息
        adminDao.updateById(status);
        //模拟异常
        int a =10/0;
    }
}

ligeramente 修改测试类TestUserServiceImpl:

@Service
@Slf4j
public class TestUserServiceImpl extends ServiceImpl<TestUserDao, TestUser> implements TestUserService {
    @Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
    @Resource
    private AdminServiceImpl adminService;
    /**
     * insertData作为方法调用者,测试同类下的事务嵌套
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
        //同类下的updateStatus方法
            updateStatus(testUser.getId());
         //另一个类下的updateStatus方法
         //   adminService.updateStatus(testUser.getId());
        }catch (Exception e) {
            throw new MyException(501,"模拟异常");
        }
        return result;
        
    }
​
    
    /**
     * 被调用方法,加入Propagation.NOT_SUPPORTED
     * @param id 查询数据库id
     * @return 更新id对应admin的状态
     */
    @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        log.info("更新对象id为-》{}的状态",id);
        adminDao.update(adminDao.selectById(id), new LambdaUpdateWrapper<Admin>().set(Admin::getStatus,1).eq(Admin::getId,id));
        //模拟异常
        int a = 10/0;
    }
    
}

En este punto 同一个方法内部存在事务嵌套时->insertData调用同类下的updateStatus方法, pruebe la identificación que existe en una base de datos, verifique los resultados de la inserción y actualización de datos:

 @Autowired
    private TestUserServiceImpl testUserService;
​
    private static final TestUser tu =new TestUser();
    static {
        tu.setId(202).setName("第202号测试");
    }
    
    @Test
     void requiredResult(){
        System.out.println("处理的执行结果:"+testUserService.insertData(tu));
    }

703b407e6b4f4bc4ad4de04be8af42ae.png

 Obviamente, aunque Not_Supportedel método no se ejecutará en la transacción, debido a que insertDatahay una transacción en el método externo, el siguiente método aún se une a la transacción, por lo que todo el método de prueba se revierte. Por lo tanto, si las transacciones están anidadas en la misma clase, el resultado final depende de las características de propagación de la transacción del método externo .

Luego pruebe si el método anidado es un método en otra clase:

 /**
     * insertData作为方法调用者,测试不同类下的事务嵌套
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
        //同类下的updateStatus方法
         //   updateStatus(testUser.getId());
         //另一个类下的updateStatus方法
         adminService.updateStatus(testUser.getId());
        }catch (Exception e) {
            throw new MyException(501,"模拟异常");
        }
        return result;
    }

9bce36d471174add85df491e8f057447.png

 Se puede ver que aunque el método de actualización tiene una excepción, la actualización aún es exitosa y el método insertData se revierte. La razón es: después de que el método de inserción de datos se ejecuta con éxito, se ejecuta en un estado no transaccional porque se modifica, es decir, se actualiza primero y luego se produce una excepción sin retroceder 被调用的不同类下的更新操作的方法updateStatus. Not_SupportedLuego, cuando se ejecutó el método insertData externo, se encontró la excepción. Aunque se lanzó la excepción, debido a que el comportamiento de propagación de la transacción del método externo era yes, REQUIREDtodavía tomó la transacción como un todo, por lo que se revirtió. Pero la actualización aún se realizó con éxito.

Es decir当不同类的事务存在嵌套的时候,外层方法按照外层的事务传播行为执行,内层的方法按照内层的传播行为去执行。同类与不同类下的事务嵌套执行方式是不同的

Propagación.NUNCA

Esto es obvio El método modificado por este comportamiento de propagación no puede ejecutarse en el contexto de una transacción, de lo contrario, se lanzará una excepción.

@Service
@Slf4j
public class TestUserServiceImpl extends ServiceImpl<TestUserDao, TestUser> implements TestUserService {
    @Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
    @Resource
    private AdminServiceImpl adminService;
    /**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        //这里并没有捕获可能出现的异常,不然就看不到结果了
        adminService.updateStatus(testUser.getId());
        return result;
    }
}
​
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin>
    implements AdminService{
  @Resource
  private AdminDao adminDao;
    /**
     * 测试Propagation.NEVER
     * @param id
     */
    @Transactional(propagation = Propagation.NEVER,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        Admin status = Admin.builder().id(id).status((byte) 1).build();
        adminDao.updateById(status);
        int a =10/0;
    }
}

Dado que el método externo tiene una transacción y el método llamado no se puede ejecutar en un método con un contexto de transacción, se lanza directamente la siguiente excepción:

org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
​
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.handleExistingTransaction(AbstractPlatformTransactionManager.java:413)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:352)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:574)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:361)
    ……

Propagación.ANIDADO

Este comportamiento de propagación de transacciones también se denomina transacción anidada y sus funciones específicas son las siguientes:

  1. Indica que ya existe una transacción para el método actual, luego el método se ejecutará en una transacción anidada.

  2. Las transacciones anidadas se pueden confirmar o revertir independientemente de la transacción actual.

  3. Si la transacción actual no existe, se comporta como Propagation_Required.

El concepto de transacciones anidadas es que la transacción interna depende de la transacción externa. Cuando la transacción externa falla, la acción realizada por la transacción interna se revertirá. La falla de la operación de transacción interna no provocará la reversión de la transacción externa.

Por lo tanto, cuando no hay transacción externa y se NESTEDmodifica el método interno, es consistente con lo dicho por 3:

@Service
@Slf4j
public class TestUserServiceImpl extends ServiceImpl<TestUserDao, TestUser> implements TestUserService {
    @Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
    @Resource
    private AdminServiceImpl adminService;
    /**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
            adminService.updateStatus(testUser.getId());
        }catch (Exception e) {
            throw new MyException(501,"模拟异常");
        }
        return result;
    }
}
​
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin>
    implements AdminService{
  @Resource
  private AdminDao adminDao;
    /**
     * 测试Propagation.NESTED
     * @param id
     */
    @Transactional(propagation = Propagation.NESTED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        Admin status = Admin.builder().id(id).status((byte) 1).build();
        adminDao.updateById(status);
        int a =10/0;
    }
}

6a30afc4b5d84306a8c2b894f70ebdea.png

 Cuando el método externo tiene cosas, en este momento,如果外部方法发生异常,则内部事务一起发生回滚操作;2,如果外部无异常情况,内部被调用方法存在异常情况,则内部方法独立回滚;

Aquí nos enfocamos en probar el segundo caso de reversión interna independiente.Las condiciones de prueba son las siguientes:

@Service
@Slf4j
public class TestUserServiceImpl extends ServiceImpl<TestUserDao, TestUser> implements TestUserService {
    @Resource
    private TestUserDao testUserDao;
​
    @Resource
    private AdminDao adminDao;
    @Resource
    private AdminServiceImpl adminService;
    /**
     * insertData作为方法调用者
     * @param testUser 新对象
     * @return 方法执行结果
     */
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    public String insertData(TestUser testUser) {
        testUser.setCreateTime(new Date());
        String result=testUserDao.insert(testUser)>0?"数据插入成功":"数据插入失败";
        log.info("获取数据插入结果->{}",result);
        log.info("更新状态");
        try {
        //外部捕获异常情况
            adminService.updateStatus(testUser.getId());
        }catch (Exception e) {
             e.printStackTrace();
        }
        return result;
    }
}
​
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminDao, Admin>
    implements AdminService{
  @Resource
  private AdminDao adminDao;
    /**
     * 测试Propagation.NESTED
     * @param id
     */
    @Transactional(propagation = Propagation.NESTED,rollbackFor = MyException.class)
    public void updateStatus(Integer id) {
        Admin status = Admin.builder().id(id).status((byte) 1).build();
        adminDao.updateById(status);
        //内部方法模拟抛出异常
        throw new MyException(507,"模拟异常");
    }
}

En este momento, hay una transacción en el método externo, y se NESTEDmodifica el método interno, entonces 执行后外部的业务会正常执行,而有异常抛出却被调用的内部方法会独立回滚.  af5230b2afce49a484a69fc01a270800.png

4. Resumen de escenarios de falla de transacción

En el desarrollo real de JavaWeb, puede haber escenarios en los que fallan las transacciones de Spring.Algunas situaciones comunes incluyen:

  1. El método no está marcado con la anotación @Transactional: Spring usa la anotación @Transactional para declarar la transacción.Si el método no está marcado con la anotación, la transacción no tendrá efecto.

    Solución: agregue la anotación @Transactional al método que necesita para abrir la transacción para asegurarse de que la transacción pueda funcionar normalmente.

  2. La excepción no es manejada por la captura y la reversión de la transacción no es válida: cuando se lanza una excepción, Spring automáticamente revertirá la transacción, pero si la excepción es capturada y procesada, y no se lanza nuevamente, la transacción no será retrotraído. Por ejemplo, lo siguiente:

    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class) 
        public void updateStatus(Integer id) { 
            try { 
                Admin status = Admin.builder().id(id).status((byte) 1).build() ; 
                adminDao.updateById(estado); 
                throw new MyException(507,"模拟异常"); 
            }atrapar (Excepción e){ 
            } 
         }

    Aunque la excepción se lanzó anteriormente, se detectó nuevamente, por lo que la excepción admitida por la transacción Spring debe lanzarse nuevamente. (Escrito estúpidamente, solo para demostración)

    Solución: use throw para lanzar una excepción en el bloque catch para asegurarse de que la transacción se pueda revertir.

  3. La transacción abarca múltiples métodos o clases: cuando una transacción involucra múltiples métodos o clases, si uno de los métodos o clases no configura la transacción correctamente, toda la transacción puede fallar.

    Solución: asegúrese de que todos los métodos o clases involucrados en las transacciones estén configurados correctamente con transacciones y utilice los mecanismos de propagación de transacciones adecuados.

  4. Se usan múltiples fuentes de datos: cuando se usan múltiples fuentes de datos, es posible que las transacciones no funcionen correctamente si el administrador de transacciones no está configurado correctamente o no especifica qué fuente de datos usar.

    Solución: configure el administrador de transacciones correspondiente para cada fuente de datos y asegúrese de que el administrador de transacciones especifique correctamente qué fuente de datos usar.

  5. El modificador de acceso al método de transacción no es público; de lo contrario, la transacción fallará

    La razón también es muy simple, porque como se mencionó anteriormente, el mecanismo de transacción de Spring se implementa esencialmente mediante un proxy dinámico.Para el proxy dinámico de JDK, solo puede representar clases que implementan interfaces, y las interfaces son públicas de forma predeterminada. Cglib no puede representar el método privado, por lo que la transacción fallará.

  6. El método de transacción es static, finalla transacción modificada tampoco será válida

    Además, debido a que las transacciones declarativas de Spring se implementan en base a proxies dinámicos, es imposible reescribir los métodos finales modificados; ya sean proxies dinámicos JDK o proxies dinámicos Cglib, el objeto específico del proxy debe obtenerse a través del proxy y el método estático El modificado El método pertenece al objeto de clase, no a ningún objeto de instancia, por lo que el método estático no se puede anular, es decir, el método estático no se puede enviar por proxy de forma dinámica.

  7. Si la tabla de la base de datos que se está operando no admite transacciones, la configuración de las transacciones de Spring también fallará.Por ejemplo, su motor de almacenamiento mysql es MyISAM.

Cinco escenarios de uso del mecanismo de propagación de transacciones

El mecanismo de propagación de transacciones puede brindar comodidad en los siguientes escenarios de desarrollo:

  • En el caso de requisitos de alta coherencia de datos entre varios métodos, el mecanismo de propagación REQUERIDO se puede utilizar para garantizar que los métodos se ejecuten en la misma transacción.

  • Si necesita abrir una nueva transacción en un método y administrarla independientemente de las transacciones externas, puede usar el mecanismo de propagación REQUIRES_NEW.

  • Cuando se requieren transacciones anidadas y se pueden revertir independientemente de las transacciones externas, se puede usar el mecanismo de propagación NESTED.

En resumen, el mecanismo de propagación de transacciones en Spring Framework proporciona medios de control de transacciones flexibles, que pueden seleccionar el comportamiento de propagación adecuado según las necesidades específicas, simplificar el proceso de desarrollo, garantizar la coherencia de los datos y proporcionar funciones de gestión de transacciones de alto nivel. Las insuficiencias son bienvenidas a corregir ~

 

Supongo que te gusta

Origin blog.csdn.net/qq_42263280/article/details/131605310
Recomendado
Clasificación