Spring @Transactional - aislamiento, propagación

447

¿Alguien puede explicar para qué sirven los parámetros de aislamiento y propagación en la @Transactionalanotación a través del ejemplo del mundo real?

Básicamente, cuándo y por qué debería elegir cambiar sus valores predeterminados.

Mat B.
fuente

Respuestas:

442

Buena pregunta, aunque no es trivial para responder.

Propagación

Define cómo se relacionan las transacciones 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.

  • Read Uncommitted: Permite lecturas sucias.
  • Read Committed: No permite lecturas sucias.
  • Repeatable Read: Si una fila se lee dos veces en la misma transacción, el resultado siempre será el mismo.
  • Serializable: Realiza todas las transacciones en una secuencia.

Los diferentes niveles tienen diferentes características de rendimiento en una aplicación multiproceso. Creo que si entiendes el dirty readsconcepto podrás 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, podría ser un valor predeterminado sensato (si se puede reclamar) Read Committed, que solo le permite leer valores que ya han sido confirmados por otras transacciones en ejecución, en combinación con un nivel de propagación de Required. Entonces 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 a la provideServicerutina y se completará al salir:

public class FooService {
    private Repository repo1;
    private Repository repo2;

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

Si lo hubiésemos utilizado 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 a rollbackpodrí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 que fooService.provideService()se NO revertido ya que crea su propio sub-transacción.

  • Required: esperaríamos que todo se revierta y la tienda de respaldo no haya cambiado.

Johan Sjöberg
fuente
¿Cómo se relaciona ese último enlace con lo que estás hablando? Según los documentos vinculados, es la sesión la que parece indicar cuál es la transacción actual, no la fábrica de sesiones.
Donal Fellows
@Donal, perdón, eso no estaba claro. Mi punto era que, dado que sessionFactory.getCurrentTransaction()se agregó, no hay necesidad de ejecutar HibernateTemplatemás para administrar las transacciones. Lo eliminé :)
Johan Sjöberg
Mi pregunta era sobre dónde apuntaba el enlace, en realidad. :-)
Donal Fellows
cómo obtener los cambios realizados en transacción- actual stackoverflow.com/questions/36132667/...
Prasanna Kumar HA
304

PROPAGATION_REQUIRED = 0 ; Si DataSourceTransactionObject T1 ya se ha iniciado para el Método M1.Si se requiere otro objeto de transacción Método M2, no se crea un nuevo objeto de transacción. 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 existente 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 progreso (ejecutando el método M1). Si otro método M2 comienza a ejecutarse, T1 se suspende por la duración del método M2 con el nuevo DataSourceTransactionObject T2 para M2.M2 ejecutado dentro de su propio contexto de transacción

PROPAGATION_NOT_SUPPORTED = 4 ; Si DataSourceTransactionObject T1 ya se inició para el Método M1.Si otro método M2 se ejecuta simultáneamente, entonces M2 no debería ejecutarse dentro del contexto de la transacción. T1 se suspende hasta que M2 finaliza.

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 concurrentes. Es compatible con la coherencia que deja los datos en muchas tablas en un estado coherente. Implica bloquear filas y / o tablas en una base de datos.

El problema con transacciones múltiples

Escenario 1. Si la transacción T1 lee datos de la tabla A1 que fue escrita por otra transacción concurrente T2. Si en el camino T2 es reversión, 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 revierte, entonces a = 1 se revertirá 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 los datos de la tabla A1.Si otra transacción concurrente (T2) actualiza los datos de la tabla A1.Los datos que T1 ha leído son diferentes de la tabla A1. Porque T2 ha actualizado los datos de la tabla A1.Eg si T1 lea a = 1 y T2 actualizado a = 2. Luego 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 de las filas en la tabla A1

El escenario 1 se llama lecturas sucias.

El escenario 2 se llama lecturas no repetibles.

El escenario 3 se llama lecturas fantasma.

Por lo tanto, el nivel de aislamiento es la extensión a la que se pueden evitar el escenario 1, el escenario 2 y el escenario 3 . Puede obtener un nivel de aislamiento completo mediante la implementación del bloqueo. Esto evita que se produzcan lecturas y escrituras concurrentes 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 el Escenario 1, Escenario 2, Escenario 3

ISOLATION_READ_COMMITTED : permite lecturas de transacciones concurrentes que se han confirmado. Puede sufrir el Escenario 2 y el Escenario 3. Debido a que otras transacciones pueden estar actualizando los datos.

ISOLATION_REPEATABLE_READ : varias lecturas del mismo campo producirán los mismos resultados hasta que se modifiquen por sí mismas. Puede sufrir el Escenario 3. Debido a que otras transacciones pueden estar insertando los datos

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

Puedes 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 para aislamiento y propagación.

abishkar bhattarai
fuente
cómo obtener los cambios realizados en transacción- actual stackoverflow.com/questions/36132667/...
Prasanna Kumar HA
2
¿Cuál es la interacción entre el nivel de aislamiento y la propagación ? Si el método 1 inicia una transacción con nivel de aislamiento, digamos, READ_COMMITTED, y luego llama al método2 con el nivel REPEATABLE_READ, seguramente el método 2 debe ejecutarse en su propia transacción nueva, independientemente del comportamiento de propagación que especifique (por ejemplo, SOLO REQUERIDO)?
Cornel Masson
Esto es realmente tarde para el programa, pero cuando PROPAGATION_REQUIRES_NEW, ¿qué sucede con T1 (que es usado por M1) si otra llamada nueva le sucede a M1? (decir M1.1)
Tim Z.
115

Una explicación suficiente sobre cada parámetro viene dada por otras respuestas; Sin embargo, solicitó un ejemplo del mundo real, este es el que aclara el propósito de las diferentes opciones de propagación :

Supongamos 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 inscribir al usuario y otro para enviar correos electrónicos, que este último se llama 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 del tipo de propagación REQUIRES_NEW y, además, es probable que arroje una excepción (servidor SMTP inactivo, correo electrónico no válido u otros motivos). 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
}

Esto significa que cada vez que se crea un objeto DAO y, por lo tanto, un acceso potencial a db, debemos asegurarnos de que la llamada se realizó desde uno de nuestros servicios, lo que implica que debe existir una transacción en vivo; de lo contrario, se produce una excepción. Por lo tanto, la propagación es de tipo OBLIGATORIO .

ye9ane
fuente
26
Ejemplo perfecto para REQUIRES_NEW.
Ravi Thapliyal
55
¡Buena explicación! Por cierto, ¿cuál es el valor predeterminado para la propagación? También sería aún mejor si pudieras dar un ejemplo como este también para el aislamiento. Muchas gracias.
Prakash K
55
@PrakashK El valor predeterminado es REQUERIDO. ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… )
ihebiheb
59

El nivel de aislamiento define cómo los cambios realizados en un repositorio de datos por una transacción afectan otras transacciones simultáneas simultáneas, y también cómo y cuándo esos datos modificados están disponibles para otras transacciones. Cuando definimos una transacción usando Spring Framework, también podemos configurar en qué nivel de aislamiento se ejecutará esa misma transacción.

@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {

}

El nivel de aislamiento READ_UNCOMMITTED indica que una transacción puede leer datos que aún no han sido confirmados por otras transacciones.

El nivel de aislamiento READ_COMMITTED indica que una transacción no puede leer datos que otras transacciones aún no han confirmado.

El nivel de aislamiento REPEATABLE_READ establece que si una transacción lee un registro de la base de datos varias veces, el resultado de todas esas operaciones de lectura siempre debe ser el mismo.

El nivel de aislamiento SERIALIZABLE es el más restrictivo de todos los niveles de aislamiento. Las transacciones se ejecutan con bloqueo en todos los niveles (bloqueo de lectura, rango y escritura) para que parezca que se ejecutaron de forma serializada.

La propagación es la capacidad de decidir cómo deben encapsularse los métodos comerciales en las transacciones lógicas o físicas.

El comportamiento REQUERIDO de Spring significa que se usará la misma transacción si ya hay una transacción abierta en el contexto de ejecución del método de bean actual.

El comportamiento de REQUIRES_NEW significa que el contenedor siempre creará una nueva transacción física.

El comportamiento NESTED hace que las transacciones Spring anidadas utilicen la misma transacción física, pero establece puntos de guardado entre invocaciones anidadas para que las transacciones internas también puedan revertirse independientemente de las transacciones externas.

El comportamiento OBLIGATORIO establece que una transacción abierta existente ya debe existir. Si no, el contenedor arrojará una excepción.

El comportamiento NUNCA indica que una transacción abierta existente no debe existir ya. Si existe una transacción, el contenedor generará una excepción.

El comportamiento NOT_SUPPORTED se ejecutará fuera del alcance de cualquier transacción. Si ya existe una transacción abierta, se pausará.

El comportamiento SOPORTES se ejecutará en el ámbito de una transacción si ya existe una transacción abierta. Si no hay una transacción ya abierta, el método se ejecutará de todos modos, pero de manera no transaccional.

reos
fuente
44
Si pudiera agregar cuándo usar cuál, sería mucho más beneficioso.
Kumar Manish
Dé algunos ejemplos, sería muy útil para los principiantes
nitinsridar
23

Una transacción representa una unidad de trabajo con una base de datos.

En la TransactionDefinitioninterfaz de Spring que define las propiedades de transacción compatibles con Spring. @TransactionalLa anotación describe los atributos de transacción en un método o clase.

@Autowired
private TestDAO testDAO;

@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {

  // Interact with testDAO

}

Propagación (reproducción): se utiliza para la relación entre transacciones. (análogo a la comunicación entre hilos de Java)

+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value |        Propagation        |                                             Description                                              |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
|    -1 | TIMEOUT_DEFAULT           | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
|     0 | PROPAGATION_REQUIRED      | Support a current transaction; create a new one if none exists.                                      |
|     1 | PROPAGATION_SUPPORTS      | Support a current transaction; execute non-transactionally if none exists.                           |
|     2 | PROPAGATION_MANDATORY     | Support a current transaction; throw an exception if no current transaction exists.                  |
|     3 | PROPAGATION_REQUIRES_NEW  | Create a new transaction, suspending the current transaction if one exists.                          |
|     4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally.                     |
|     5 | PROPAGATION_NEVER         | Do not support a current transaction; throw an exception if a current transaction exists.            |
|     6 | PROPAGATION_NESTED        | Execute within a nested transaction if a current transaction exists.                                 |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+

Aislamiento: El aislamiento es una de las propiedades ACID (Atomicidad, Consistencia, Aislamiento, Durabilidad) de las transacciones de la base de datos. El aislamiento determina cómo la integridad de la transacción es visible para otros usuarios y sistemas. Se utiliza para el bloqueo de recursos, es decir, el control de concurrencia, asegúrese de que solo una transacción pueda acceder al recurso en un punto dado.

Percepción de bloqueo: el nivel de aislamiento determina la duración de la retención.

+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode      |  Read             |   Insert    |   Update    |       Lock Scope       |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED          |  uncommitted data | Allowed     | Allowed     | No Lock                |
| READ_COMMITTED (Default)  |   committed data  | Allowed     | Allowed     | Lock on Committed data |
| REPEATABLE_READ           |   committed data  | Allowed     | Not Allowed | Lock on block of table |
| SERIALIZABLE              |   committed data  | Not Allowed | Not Allowed | Lock on full table     |
+---------------------------+-------------------+-------------+-------------+------------------------+

Percepción de lectura: se producen los siguientes 3 tipos de problemas principales:

  • Lecturas sucias : lee datos no confirmados de otro tx (transacción).
  • Lecturas no repetibles : lecturas confirmadas UPDATESdesde otro tx.
  • Lecturas fantasma : lecturas comprometidas INSERTSy / o DELETESde otro tx

Niveles de aislamiento con diferentes tipos de lecturas:

+---------------------------+----------------+----------------------+----------------+
| Isolation Level Mode      |  Dirty reads   | Non-repeatable reads | Phantoms reads |
+---------------------------+----------------+----------------------+----------------+
| READ_UNCOMMITTED          | allows         | allows               | allows         |
| READ_COMMITTED (Default)  | prevents       | allows               | allows         |
| REPEATABLE_READ           | prevents       | prevents             | allows         |
| SERIALIZABLE              | prevents       | prevents             | prevents       |
+---------------------------+----------------+----------------------+----------------+

por ejemplo

Premraj
fuente
20

Casi nunca quieres usarlo Read Uncommitedya que no es realmente ACIDcompatible. Read CommmitedEs un buen lugar de inicio predeterminado. Repeatable Readprobablemente solo sea necesario en escenarios de informes, resumen o agregación. Tenga en cuenta que muchos DB, incluidos los postgres, en realidad no son compatibles con la lectura repetible, Serializablesino que debe usarlos . Serializablees útil para cosas que sabes que tienen que suceder completamente independientemente de cualquier otra cosa; Piense en ello como synchronizeden Java. Serializable va de la mano con la REQUIRES_NEWpropagación.

Utilizo REQUIRESpara todas las funciones que ejecutan consultas UPDATE o DELETE, así como funciones de nivel de "servicio". Para las funciones de nivel DAO que solo ejecutan SELECT, utilizo el SUPPORTScual participará en un TX si ya se ha iniciado (es decir, se llama desde una función de servicio).

IraPayaso
fuente
13

El aislamiento de la transacción y la propagación de la transacción, aunque están relacionados, son claramente dos conceptos muy diferentes. En ambos casos, los valores predeterminados se personalizan en el componente de límite del cliente mediante el uso de la gestión de transacciones declarativas o la gestión de transacciones programáticas . Los detalles de cada nivel de aislamiento y atributos de propagación se pueden encontrar en los enlaces de referencia a continuación.

Aislamiento de transacciones

Para dos o más transacciones / conexiones en ejecución a una base de datos, cómo y cuándo los cambios realizados por consultas en una transacción impactan / son visibles para las consultas en una transacción diferente. También se relacionó con qué tipo de bloqueo de registros de la base de datos se utilizará para aislar los cambios en esta transacción de otras transacciones y viceversa. Esto generalmente se implementa mediante la base de datos / recurso que participa en la transacción.

.

Propagación de transacciones

En una aplicación empresarial para cualquier solicitud / procesamiento dado, hay muchos componentes involucrados para realizar el trabajo. Algunos de estos componentes marcan los límites (inicio / final) de una transacción que se utilizará en el componente respectivo y sus subcomponentes. Para este límite transaccional de componentes, la Proposición de transacción especifica si el componente respectivo participará o no en la transacción y qué sucede si el componente que llama ya tiene o no tiene una transacción ya creada / iniciada. Esto es lo mismo que los atributos de transacción Java EE. Esto generalmente lo implementa el administrador de transacciones / conexiones del cliente.

Referencia:

Gladwin Burboz
fuente
1
Genial, toda la información en un solo lugar, los enlaces son muy útiles, gracias @Gladwin Burboz
nitinsridar
7

He corrido outerMethod, method_1y method_2con diferente modo de propagación.

A continuación se muestra la salida para diferentes modos de propagación.

  • Método externo

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }
  • Método 1

    @Transactional(propagation=Propagation.MANDATORY)
    public void method_1() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "XXX");
            session.save(entity);
            System.out.println("Method - 1 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
  • Método_2

    @Transactional()
    @Override
    public void method_2() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "CCC");
            session.save(entity);
            int i = 1/0;
            System.out.println("Method - 2 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
      • externalMethod: sin transacción
      • method_1 - Propagation.MANDATORY) -
      • método_2: solo anotación de transacción
      • Salida: method_1 arrojará la excepción de que no hay transacción existente
      • externalMethod: sin transacción
      • method_1: solo anotación de transacción
      • método_2 - Propagación.MANDATORIO)
      • Salida: método_2 arrojará excepción de que no hay transacción existente
      • Salida: método_1 persistirá registro en la base de datos.
      • externalMethod: con la transacción
      • method_1: solo anotación de transacción
      • método_2 - Propagación.MANDATORIO)
      • Salida: método_2 persistirá registro en la base de datos.
      • Salida: método_1 persistirá registro en la base de datos. - Aquí, la transacción existente principal externa utilizada para los métodos 1 y 2
      • externalMethod: con la transacción
      • method_1 - Propagation.MANDATORY) -
      • método_2: solo anotación de transacción y genera una excepción
      • Salida: ningún registro persiste en la base de datos significa que se hizo la reversión.
      • externalMethod: con la transacción
      • método_1 - Propagación.REQUIRES_NUEVO)
      • method_2 - Propagation.REQUIRES_NEW) y lanza una excepción 1/0
      • Salida: method_2 arrojará una excepción, por lo que el registro method_2 no persistió.
      • Salida: método_1 persistirá registro en la base de datos.
      • Salida: no hay reversión para el método_1
NIrav Modi
fuente
3

Podemos agregar para esto:

@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {

    public Customer getDetail(String customername) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateCustomer(Customer customer) {
        // do something
    }
}
Ankit
fuente
1

Puedes usar así:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}

Puedes usar esto también:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
Ankit
fuente