@Escenario de error de anotación transaccional y versión de la solución

Escenario de fracaso

1 La base de datos primero debe admitir transacciones:

Tomando mysql como ejemplo, su motor MyISAM no admite operaciones de transacciones, mientras que InnoDB solo admite transacciones. A partir de mysql5.5, el motor predeterminado es InnoDB y el predeterminado anterior era MyISAM.

2. La fuente de datos no tiene configurado un administrador de transacciones

3. No gestionado en primavera.

Las anotaciones de transacción se agregan a la capa de servicio. Si no hay una anotación @Service, esta clase no se cargará en un Bean, entonces esta clase no será administrada por Spring y la transacción naturalmente fallará.

4. El método no es público.

Según el sitio web oficial de Spring, @Transactional solo se puede usar en métodos públicos; de lo contrario, la transacción no tendrá efecto. Si debe usarse en métodos no públicos, puede habilitar el modo proxy AspectJ. AspectJ utiliza tejido en tiempo de carga para admitir todos los puntos de corte, por lo que puede admitir configuraciones de transacciones para métodos internos.

5.@Error de configuración de propagación del atributo de anotación transaccional

Si la configuración de anotación no admite transacciones, se utiliza la propagación. NOT_SUPPORTED: Indica que no es fácil ejecutar la transacción, si actualmente hay una transacción, se suspenderá.

6. Las llamadas a métodos en la misma clase hacen que @Transactional deje de ser válido.

Durante el desarrollo, es inevitable llamar a métodos en la misma clase. Por ejemplo, hay una clase Test, que tiene un método a. A luego llama al método b de esta clase (independientemente de si el método b se modifica con público o privado). , pero el método A no. Declara la transacción de anotación y el método b la tiene. Después de llamar al método A externamente, la transacción del método B no tendrá efecto. Este es también un lugar donde a menudo se cometen errores.

Entonces, ¿por qué pasa ésto? De hecho, todavía se debe al uso del proxy Spring AOP, porque solo cuando el código fuera de la clase actual llama al método de transacción, será administrado por el objeto proxy generado por Spring.

 
    //@Transactional
    @GetMapping("/test")
    private void a(User user) throws Exception {
    
    
        userMapper.insert(user);
        b(user);
    }
 
    @Transactional
    public void b(User user) throws Exception {
    
    
       doSomething();
    }

Reponer:

Si el método a también está anotado con @Transactional, ¿la transacción entrará en vigor en este momento?

   上面的代码只是放开a方法上的@Transactional注解,此时a方法的事务依然是无法生效的。我们看到在事务方法a中,直接调用事务方法b。从前面介绍的内容可知,b方法拥有事务是因为spring aop生成了代理对象,但是这种方法直接调用了this对象的方法,所以a方法不会生成事务。

Se puede ver que llamar directamente a métodos de la misma clase internamente hará que la transacción falle.

Entonces la pregunta es, ¿cómo solucionar este escenario?

Método 1: agregar un nuevo método de servicio

Este método es muy simple: solo necesita agregar un nuevo método de Servicio, agregar la anotación @Transactional al nuevo método de Servicio y mover el código que requiere la ejecución de la transacción al nuevo método.

​
​
@Servcie 
public class ServiceA {
    
     
   @Autowired 
   prvate ServiceB serviceB; 
 
   public void save(User user) {
    
     
         queryData1(); 
         queryData2(); 
         serviceB.doSave(user); 
   } 
 } 
 
 @Servcie 
 public class ServiceB {
    
     
 
    @Transactional(rollbackFor=Exception.class) 
    public void doSave(User user) {
    
     
       userMapper.insert(user); 
       b(user); 
    } 
 
 } 
 
​
 
​

Método 2: Inyéctese en la clase de Servicio

Si no desea agregar una nueva clase de Servicio, inyectarse usted mismo en la clase de Servicio también es una opción.

​
​
​
@Servcie 
public class ServiceA {
    
     
   @Autowired 
   prvate ServiceA serviceA; 
 
   public void save(User user) {
    
     
         queryData1(); 
         queryData2(); 
         serviceA.doSave(user); 
   } 
 
   @Transactional(rollbackFor=Exception.class) 
    public void doSave(User user) {
    
     
       userMapper.insert(user); 
       b(user); 
    } 
 }

Este enfoque no causará problemas de dependencia circular. De hecho, el caché de tercer nivel dentro de Spring IOC garantiza que no habrá problemas de dependencia circular.

Método 3: a través de la clase AopContext

Introducir dependencias

<dependency>
   <groupId>org.aspectj</groupId>
  <artifactId>aspectjweaver</artifactId>
</dependency>

Habilite la adquisición de AOP (agregue a la clase de inicio SpringBoot o clase de configuración)

@EnableAspectJAutoProxy(exposeProxy = true)

Utilice AopContext.currentProxy() en la clase de Servicio para obtener el objeto proxy

@Servcie 
public class ServiceA {
    
     
  
   public void save(User user) {
    
     
         queryData1(); 
         queryData2(); 
         ((ServiceA)AopContext.currentProxy()).doSave(user); 
   } 
 
   @Transactional(rollbackFor=Exception.class) 
    public void doSave(User user) {
    
     
       userMapper.insert(user); 
       b(user); 
    } 
 } 

¿Cómo se puede revertir la transacción si la excepción se come y luego no se lanza?

Si desea que las transacciones de Spring se reviertan normalmente, debe generar una excepción que pueda manejar. Si no se lanza ninguna excepción, Spring considera que el programa es normal.

Para decirlo sin rodeos, incluso si el desarrollador no detecta manualmente la excepción, si la excepción lanzada es incorrecta, la transacción de primavera no se revertirá.

Debido a que las transacciones de primavera solo revierten RuntimeException (excepción de tiempo de ejecución) y Error (error) de forma predeterminada, no revertirá la excepción ordinaria (excepción que no es de tiempo de ejecución).

7. Come la excepción mediante try catch.

8. Error de tipo de excepción [@Reversión del atributo de anotación transaccional para error de configuración]

La reversión predeterminada es RuntimeException. Si desea activar otras reversiones de excepción, debe configurar la anotación.

@Transactional(rollbackFor = Exception.class)

9. Los métodos se modifican con final.

A veces, un método no quiere ser anulado por subclases, en este caso el método se puede definir como final. No hay ningún problema en definir el método ordinario de esta manera, pero si la transacción se define como final, esto hará que la transacción falle.

¿por qué?

   spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。

   但如果某个方法被final修饰了,那么在它的代理类中,就无法重写该方法,事务也就失效了。

Nota: Si un método es estático, no se puede convertir en un método de transacción a través de un proxy dinámico.

10.Llamadas multiproceso

En el desarrollo de proyectos reales, todavía existen muchos escenarios de uso para subprocesos múltiples. ¿Habrá algún problema si las transacciones de Spring se utilizan en escenarios de subprocesos múltiples?

@Slf4j 
@Service 
public class UserService {
    
     
 
    @Autowired 
    private UserMapper userMapper; 
    @Autowired 
    private RoleService roleService; 
 
    @Transactional 
    public void add(UserModel userModel) throws Exception {
    
     
        userMapper.insertUser(userModel); 
        new Thread(() -> {
    
     
            roleService.doOtherThing(); 
        }).start(); 
    } 
} 
 
@Service 
public class RoleService {
    
     
 
    @Transactional 
    public void doOtherThing() {
    
     
        System.out.println("保存role表数据"); 
    } 
} 

En el ejemplo anterior, podemos ver que el método de transacción doOtherThing se llama en el método de transacción agregar, pero el método de transacción doOtherThing se llama en otro hilo.

Esto dará como resultado que los dos métodos no estén en el mismo subproceso y obtengan conexiones de bases de datos diferentes, lo que dará como resultado dos transacciones diferentes. Si se produce una excepción en el método doOtherThing, es imposible revertir el método add.

Si ha leído el código fuente de las transacciones de Spring, es posible que sepa que las transacciones de Spring se implementan a través de conexiones de bases de datos. Se guarda un mapa en el hilo actual, la clave es la fuente de datos y el valor es la conexión de la base de datos.

La misma transacción de la que estamos hablando en realidad se refiere a la misma conexión de base de datos, solo con la misma conexión de base de datos podemos confirmar y revertir al mismo tiempo. Si están en hilos diferentes, las conexiones a la base de datos obtenidas deben ser diferentes, por lo que son transacciones diferentes.

Supongo que te gusta

Origin blog.csdn.net/u014212540/article/details/132203122
Recomendado
Clasificación