Saltar al contenido

Spring @Transactional – aislamiento, propagación

Solución:

Buena pregunta, aunque no trivial de responder.

Propagación

Define cómo las transacciones se relacionan entre sí. Opciones comunes:

  • REQUIRED: El código siempre se ejecutará en una transacción. Crea una nueva transacción o reutiliza una si está disponible.
  • REQUIRES_NEW: El código siempre se ejecutará en una nueva transacción. Suspende la transacción actual si existe.

Aislamiento

Define el contrato de datos entre transacciones.

  • ISOLATION_READ_UNCOMMITTED: Permite lecturas sucias.
  • ISOLATION_READ_COMMITTED: No permite lecturas sucias.
  • ISOLATION_REPEATABLE_READ: Si una fila se lee dos veces en la misma transacción, el resultado siempre será el mismo.
  • ISOLATION_SERIALIZABLE: Realiza todas las transacciones en una secuencia.

Los diferentes niveles tienen diferentes características de rendimiento en una aplicación de subprocesos múltiples. Creo que si entiendes el dirty reads concepto podrá seleccionar una buena opción.


Ejemplo de cuándo puede ocurrir una lectura sucia:

  thread 1   thread 2      
      |         |
    write(x)    |
      |         |
      |        read(x)
      |         |
    rollback    |
      v         v 
           value (x) is now dirty (incorrect)

Por lo tanto, un incumplimiento sensato (si se puede reclamar) podría ser ISOLATION_READ_COMMITTED, que solo le permite leer valores que ya han sido comprometidos por otras transacciones en ejecución, en combinación con un nivel de propagación de REQUIRED. Luego, puede trabajar desde allí si su aplicación tiene otras necesidades.


Un ejemplo práctico de dónde siempre se creará una nueva transacción al ingresar al provideService rutina y completada al salir:

public class FooService 
    private Repository repo1;
    private Repository repo2;

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void provideService() 
        repo1.retrieveFoo();
        repo2.retrieveFoo();
    

En su lugar hubiéramos usado REQUIRED, la transacción permanecería abierta si la transacción ya estaba abierta al ingresar a la rutina. Tenga en cuenta también que el resultado de una rollback podría ser diferente ya que varias ejecuciones podrían participar en la misma transacción.


Podemos verificar fácilmente el comportamiento con una prueba y ver cómo los resultados difieren con los niveles de propagación:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/fooService.xml")
public class FooServiceTests {

    private @Autowired TransactionManager transactionManager;
    private @Autowired FooService fooService;

    @Test
    public void testProvideService() 
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        fooService.provideService();
        transactionManager.rollback(status);
        // assert repository values are unchanged ... 

Con un nivel de propagación de

  • REQUIRES_NEW: esperaríamos fooService.provideService() era NO revertido desde que creó su propia subtransacción.

  • REQUIRED: esperaríamos que todo se revirtiera y que la tienda de respaldo se mantuviera sin cambios.

PROPAGATION_REQUIRED = 0; Si DataSourceTransactionObject T1 ya se inició para el método M1. Si se requiere otro objeto Transaction del Método M2, no se crea ningún objeto Transaction nuevo. El mismo objeto T1 se utiliza para M2.

PROPAGATION_MANDATORY = 2; El método debe ejecutarse dentro de una transacción. Si no hay ninguna transacción en curso, se lanzará una excepción.

PROPAGATION_REQUIRES_NEW = 3; Si DataSourceTransactionObject T1 ya se inició para el Método M1 y está en curso (ejecutando el método M1). Si otro método M2 comienza a ejecutarse, T1 se suspende mientras dure el método M2 con el nuevo DataSourceTransactionObject T2 para M2. M2 se ejecuta dentro de su propio contexto de transacción.

PROPAGATION_NOT_SUPPORTED = 4; Si DataSourceTransactionObject T1 ya se inició para el método M1. Si se ejecuta otro método M2 al mismo tiempo. Entonces M2 no debería ejecutarse dentro del contexto de la transacción. T1 se suspende hasta que se termina M2.

PROPAGATION_NEVER = 5; Ninguno de los métodos se ejecuta en el contexto de la transacción.


Un nivel de aislamiento:
Se trata de cuánto puede verse afectada una transacción por las actividades de otras transacciones simultáneas. Es compatible con la coherencia dejando los datos en muchas tablas en un estado coherente. Implica bloquear filas y / o tablas en una base de datos.

El problema de las transacciones múltiples

escenario 1. Si la transacción T1 lee datos de la tabla A1 que fueron escritos por otra transacción concurrente T2. Si en el camino T2 está retrocedido, los datos obtenidos por T1 no son válidos. Por ejemplo, a = 2 son datos originales. Si T1 lee a = 1 que fue escrito por T2. Si T2 rollback, a = 1 se retrotraerá a a = 2 en DB. Pero, ahora, T1 tiene a = 1 pero en la tabla DB se cambia a a = 2.

Escenario2. Si la transacción T1 lee datos de la tabla A1. Si otra transacción concurrente (T2) actualiza los datos en la tabla A1. Entonces, los datos que ha leído T1 son diferentes de la tabla A1. Porque T2 ha actualizado los datos de la tabla A1. Por ejemplo, si T1 lee a = 1 y T2 actualiza a = 2. Entonces a! = B.

Escenario 3. Si la transacción T1 lee datos de la tabla A1 con cierto número de filas. Si otra transacción concurrente (T2) inserta más filas en la tabla A1. El número de filas leídas por T1 es diferente al de las filas de la tabla A1.

El escenario 1 se llama Lecturas sucias.

El escenario 2 se llama Lecturas no repetibles.

El escenario 3 se llama Phantom lee.

Entonces, el nivel de aislamiento es el alcance al que Escenario 1, Escenario 2, Escenario 3 puede ser prevenido. Puede obtener un nivel de aislamiento completo implementando el bloqueo. Eso evita que se produzcan lecturas y escrituras simultáneas en los mismos datos. Pero afecta el rendimiento. El nivel de aislamiento depende de la aplicación a la aplicación cuánto aislamiento se requiere.

ISOLATION_READ_UNCOMMITTED: Permite leer los cambios que aún no se han confirmado. Sufre del Escenario 1, Escenario 2, Escenario 3.

ISOLATION_READ_COMMITTED: Permite lecturas de transacciones concurrentes que se han comprometido. Puede sufrir el Escenario 2 y el Escenario 3. Porque otras transacciones pueden estar actualizando los datos.

ISOLATION_REPEATABLE_READ: Varias lecturas del mismo campo producirán los mismos resultados hasta que se modifique por sí mismo. Puede sufrir el Escenario 3. Porque otras transacciones pueden estar insertando los datos.

ISOLATION_SERIALIZABLE: Escenario 1, Escenario 2, Escenario 3 nunca suceden. Es un completo aislamiento. Implica un bloqueo total. Afecta al rendimiento debido al bloqueo.

Puede probar usando:

public class TransactionBehaviour 
   // set is either using xml Or annotation
    DataSourceTransactionManager manager=new DataSourceTransactionManager();
    SimpleTransactionStatus status=new SimpleTransactionStatus();
   ;
  
    
    public void beginTransaction()
    
        DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
        // overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
        // set is either using xml Or annotation
        manager.setPropagationBehavior(XX);
        manager.setIsolationLevelName(XX);
       
        status = manager.getTransaction(Def);
    
    

    public void commitTransaction()
    
       
      
            if(status.isCompleted())
                manager.commit(status);
         
    

    public void rollbackTransaction()
    
       
            if(!status.isCompleted())
                manager.rollback(status);
        
    
    Main method
        beginTransaction()
        M1();
        If error()
            rollbackTransaction()
        
         commitTransaction();
    
   

Puede depurar y ver el resultado con diferentes valores de aislamiento y propagación.

Otras respuestas dan suficiente explicación sobre cada parámetro; Sin embargo, pidió un ejemplo del mundo real, aquí está el que aclara el propósito de diferentes propagación opciones:

Suponga que está a cargo de implementar un servicio de registro en el que se envía un correo electrónico de confirmación al usuario. Se le ocurren dos objetos de servicio, uno para matriculando el usuario y uno para enviando e-mails, que este último se denomina dentro del primero. Por ejemplo, algo como esto:

/* Sign Up service */
@Service
@Transactional(Propagation=REQUIRED)
class SignUpService
 ...
 void SignUp(User user)
    ...
    emailService.sendMail(User);
 


/* E-Mail Service */
@Service
@Transactional(Propagation=REQUIRES_NEW)
class EmailService
 ...
 void sendMail(User user)
  try
     ... // Trying to send the e-mail
  catch( Exception)
 

Es posible que haya notado que el segundo servicio es de tipo propagación REQUIRES_NEW y además, es probable que arroje una excepción (servidor SMTP inactivo, correo electrónico no válido u otras razones). Probablemente no desee que todo el proceso se revierta, como eliminar la información del usuario de la base de datos u otras cosas; por lo tanto, llama al segundo servicio en una transacción separada.

Volviendo a nuestro ejemplo, esta vez le preocupa la seguridad de la base de datos, por lo que define sus clases DAO de esta manera:

/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO
 // some CRUD methods

Lo que significa que cada vez que se crea un objeto DAO y, por lo tanto, un acceso potencial a la base de datos, debemos asegurarnos de que la llamada se realizó desde el interior de uno de nuestros servicios, lo que implica que debería existir una transacción en vivo; de lo contrario ocurre una excepción. Por lo tanto, la propagación es de tipo OBLIGATORIO.

valoraciones y reseñas

Agradecemos que desees apoyar nuestra tarea dejando un comentario y puntuándolo te estamos eternamente agradecidos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *