Gestión eficiente de transacciones en primavera

Buen dia a todos!

Bueno, el final del mes siempre es intenso, y solo queda un día hasta el comienzo de la segunda secuencia del curso "Developer on Spring Framework" , un curso maravilloso e interesante enseñado por el igualmente bello y enojado Yuri (como algunos estudiantes lo llaman por el nivel de requisitos en DZ), veamos otro material que hemos preparado para usted.

Vamos

Introduccion

La mayoría de las veces, los desarrolladores no otorgan importancia a la gestión de transacciones. Como resultado, o bien la mayor parte del código tiene que ser reescrito más tarde, o el desarrollador implementa la gestión de transacciones sin saber cómo debería funcionar realmente o qué aspectos deberían usarse específicamente en su caso.

Un aspecto importante en la gestión de transacciones es determinar los límites correctos de una transacción, cuándo debe comenzar una transacción y cuándo finalizar, cuándo se deben agregar datos a la base de datos y cuándo se deben bombear (en caso de una excepción).



El aspecto más importante para los desarrolladores es comprender cómo implementar mejor la gestión de transacciones en una aplicación. Así que echemos un vistazo a las diversas opciones.

Métodos de gestión de transacciones

Las transacciones se pueden administrar de las siguientes maneras:

1. Control del programa escribiendo código personalizado

Este es un antiguo método de gestión de transacciones.

EntityManagerFactory factory = Persistence.createEntityManagerFactory("PERSISTENCE_UNIT_NAME"); EntityManager entityManager = entityManagerFactory.createEntityManager(); Transaction transaction = entityManager.getTransaction() try { transaction.begin(); someBusinessCode(); transaction.commit(); } catch(Exception ex) { transaction.rollback(); throw ex; } 

Pros :

  • Los límites de transacción son obvios en el código.

Contras :

  • Es repetitivo y propenso a errores.
  • Cualquier error puede tener un gran impacto.
  • Necesita escribir muchas plantillas, también, si desea llamar a otro método desde este método, debe controlarlo nuevamente desde el código.

2. Usando Spring para la gestión transaccional

Spring admite dos tipos de gestión de transacciones

1. Gestión de transacciones de software : debe gestionar las transacciones a través de la programación. Este método es lo suficientemente flexible, pero difícil de mantener.

2. Gestión de transacciones declarativas : separa la gestión de transacciones de la lógica empresarial. Solo utiliza anotaciones en la configuración basada en XML para la gestión de transacciones.

Recomendamos encarecidamente el uso de transacciones declarativas. Si desea conocer los motivos, siga leyendo, de lo contrario, vaya directamente a la sección Gestión de transacciones declarativas si desea implementar esta opción.

Ahora veamos cada enfoque en detalle.

2.1. Gestión programática de transacciones:

Spring Framework proporciona dos herramientas para la gestión programática de transacciones.

a. Usando TransactionTemplate (recomendado por el equipo de Spring):

Veamos cómo implementar este tipo usando el código de ejemplo a continuación (tomado de la documentación de Spring con algunos cambios)

Tenga en cuenta que los fragmentos de código se toman de Spring Docs.

Archivo XML de contexto:

 <!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <!-- Definition for ServiceImpl bean --> <bean id="serviceImpl" class="com.service.ServiceImpl"> <constructor-arg ref="transactionManager"/> </bean> 

Clase de Service :

 public class ServiceImpl implements Service { private final TransactionTemplate transactionTemplate; //       PlatformTransactionManager public ServiceImpl(PlatformTransactionManager transactionManager) { this.transactionTemplate = new TransactionTemplate(transactionManager); } //       ,   ,    //       xml  this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED); this.transactionTemplate.setTimeout(30); //30  ///    public Object someServiceMethod() { return transactionTemplate.execute(new TransactionCallback() { //         public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }); }} 

Si no hay valor de retorno, use la conveniente clase TransactionCallbackWithoutResult con una clase anónima, como se muestra a continuación:

 transactionTemplate.execute(new TransactionCallbackWithoutResult() { protected void doInTransactionWithoutResult(TransactionStatus status) { updateOperation1(); updateOperation2(); } }); 

  • Las instancias de la clase TransactionTemplate son seguras para subprocesos, por lo que no todos los estados de diálogo son compatibles.
  • Sin embargo, las instancias de TransactionTemplate mantienen el estado de configuración, por lo que si una clase necesita usar una TransactionTemplate con diferentes configuraciones (por ejemplo, un nivel de aislamiento diferente), entonces debe crear dos instancias diferentes de TransactionTemplate, aunque algunas clases pueden usar la misma instancia de TransactionTemplate.

b. Usando la implementación PlatformTransactionManager directamente:

Miremos esta opción en el código nuevamente.

 <!-- Initialization for data source --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/TEST"/> <property name="username" value="root"/> <property name="password" value="password"/> </bean> <!-- Initialization for TransactionManager --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> public class ServiceImpl implements Service { private PlatformTransactionManager transactionManager; public void setTransactionManager( PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } DefaultTransactionDefinition def = new DefaultTransactionDefinition(); //     -  ,       def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { //   -  } catch (Exception ex) { txManager.rollback(status); throw ex; } txManager.commit(status); } 

Ahora, antes de pasar al siguiente método de gestión de transacciones, veamos cómo decidir qué tipo de gestión de transacciones elegir.

Elegir entre la gestión de transacciones programática y declarativa :

  • La gestión programática de transacciones es una buena opción solo si tiene un pequeño número de operaciones transaccionales. (En la mayoría de los casos, estas no son transacciones).
  • El nombre de una transacción solo se puede establecer explícitamente en Program Transaction Management.
  • La gestión programática de transacciones debe usarse cuando desea controlar explícitamente la gestión de transacciones.
  • Por otro lado, si su aplicación contiene numerosas operaciones transaccionales, vale la pena usar la gestión declarativa.
  • La gestión declarativa no le permite gestionar transacciones en lógica empresarial y no es difícil de configurar.

2.2. Transacciones declarativas (generalmente utilizadas en casi todos los escenarios de cualquier aplicación web)

Paso 1 : defina el administrador de transacciones en el archivo xml de contexto de su aplicación Spring.

 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"/> <tx:annotation-driven transaction-manager="txManager"/> 

Paso 2 : habilite el soporte de anotaciones agregando una entrada en el archivo xml de contexto de su aplicación Spring.

O agregue @EnableTransactionManagement a su archivo de configuración, como se muestra a continuación:

 @Configuration @EnableTransactionManagement public class AppConfig { ... } 

Spring recomienda anotar solo clases específicas (y métodos de clases específicas) con anotación @Transactional comparación con las interfaces de anotación.

La razón de esto es porque coloca la anotación en el nivel de la interfaz, y si usa clases proxy ( proxy-target-class = «true» ) o el aspecto entrelazado ( mode = «aspectj» ), la infraestructura proxy no reconoce los parámetros de transacción y plexos, por ejemplo, no se aplicará el comportamiento transaccional.

Paso 3 : Agregue la anotación @Transactional a la clase (método de clase) o interfaz (método de interfaz).

 <tx:annotation-driven proxy-target-class="true"> 

Configuración predeterminada: proxy-target-class="false"

  • @Transactional puede colocar una @Transactional antes de una definición de interfaz, un método de interfaz, una definición de clase o un método de clase pública.
  • Si desea que algunos métodos de clase (marcados con la anotación @Transactional ) tengan configuraciones de atributos diferentes, como el nivel de aislamiento o el nivel de propagación, coloque la anotación en el nivel de método para anular la configuración de atributos de nivel de clase.
  • En el modo proxy (que se establece de manera predeterminada), solo se pueden interceptar las llamadas a métodos "externos" que pasan por el proxy. Esto significa que una "llamada independiente", por ejemplo, un método en el destino que llama a algún otro método del objetivo, no dará como resultado una transacción real en tiempo de ejecución, incluso si el método llamado está etiquetado con @Transactional .

Ahora veamos @Transactional diferencia entre los @Transactional anotación @Transactional

@Transactional (isolation=Isolation.READ_COMMITTED)

  • El valor predeterminado es Isolation.DEFAULT
  • En la mayoría de los casos, usará la configuración predeterminada hasta que tenga requisitos especiales.
  • Le dice al administrador de transacciones ( tx ) que el siguiente nivel de aislamiento debe usarse para el tx actual. Debe instalarse en el punto desde donde comienza tx , porque no podemos cambiar el nivel de aislamiento después de que tx comienza.

PREDETERMINADO : utilice el nivel de aislamiento predeterminado en la base de datos base.

READ_COMMITTED (lectura de datos fijos): una constante que indica que se ha evitado la lectura sucia; Lectura no repetida y lectura fantasma pueden ocurrir.

READ_UNCOMMITTED (leer datos no confirmados): este nivel de aislamiento indica que una transacción puede leer datos que otras transacciones aún no han eliminado.

REPEATABLE_READ (repetibilidad de lectura): una constante que indica que se evitan lecturas sucias y lecturas no repetibles; puede aparecer la lectura fantasma.

SERIALIZABLE : Permanente, lo que indica que se evita la lectura sucia, la lectura no repetible y la lectura fantasma.

¿Qué significan estas jergas: lectura "sucia", lectura fantasma o lectura repetida?

  • Lectura sucia : la transacción A escribe. Mientras tanto, la transacción "B" lee el mismo registro hasta que se completa la transacción A. Más tarde, la transacción A decide revertir, y ahora tenemos cambios en la transacción B que son incompatibles. Esta es una lectura sucia. La transacción B funcionó en el nivel de aislamiento READ_UNCOMMITTED, por lo que podría leer los cambios realizados por la transacción A antes de que se completara la transacción.
  • Lectura no repetible : la transacción "A" lee algunos registros. Luego, la transacción "B" escribe este registro y lo confirma. Más tarde, la transacción A lee el mismo registro nuevamente y puede recibir diferentes valores, ya que la transacción B realizó cambios en este registro y los confirmó. Esta es una lectura que no se repite.
  • Lectura fantasma : la transacción "A" lee una serie de registros. Mientras tanto, la transacción "B" inserta un nuevo registro en la misma fila que la transacción A. Más tarde, la transacción A lee el mismo rango nuevamente y también recibe el registro que la transacción B acaba de insertar. Esta es una lectura fantasma: la transacción recuperó una serie de registros varias veces de la base de datos y recibió diferentes conjuntos de resultados (que contienen registros fantasma).

@Transactional(timeout=60)

El valor predeterminado es el tiempo de espera predeterminado para el sistema de transacción subyacente.

Informa al administrador de tx sobre el tiempo de espera hasta que tx esté inactivo antes de decidir si revierte las transacciones que no responden.

@Transactional(propagation=Propagation.REQUIRED)

Si no se especifica, el comportamiento de propagación predeterminado es REQUIRED .

Otras opciones son REQUIRES_NEW, MANDATORY, SUPPORTS, NOT_SUPPORTED, NEVER y NESTED .

REQUERIDO

Indica que el método de destino no puede funcionar sin un tx activo. Si tx ya se está ejecutando antes de llamar a este método, continuará en el mismo tx o el nuevo tx comenzará poco después de llamar a este método.

REQUISITOS_NUEVO

  • Indica que se debe ejecutar un nuevo tx cada vez que se llama al método de destino. Si tx ya se está ejecutando, se detendrá antes de comenzar uno nuevo.

Obligatorio

  • Indica que el método de destino requiere tx activo. Si tx no continúa, no fallará, lanzando una excepción.

SOPORTES

  • Indica que el método de destino se puede ejecutar independientemente de tx. Si tx funciona, participará en el mismo tx. Si se ejecuta sin tx, se seguirá ejecutando si no hay errores.

  • Los métodos que recuperan datos son los mejores candidatos para esta opción.

NO SOPORTADO

  • Indica que el método de destino no requiere la propagación del contexto de transacción.
  • Básicamente, los métodos que se ejecutan en una transacción, pero que realizan operaciones con RAM, son los mejores candidatos para esta opción.

Nunca

  • Indica que el método de destino generará una excepción si se ejecuta en un proceso transaccional.
  • Esta opción no se usa en la mayoría de los casos en proyectos.

@Transactional (rollbackFor=Exception.class)

Valor predeterminado: rollbackFor=RunTimeException.class

En Spring, todas las clases API lanzan una RuntimeException, lo que significa que si algún método falla, el contenedor siempre revierte la transacción actual.

El problema es solo con excepciones marcadas. Por lo tanto, este parámetro se puede usar para deshacer declarativamente una transacción si se produce una Checked Exception .

@Transactional (noRollbackFor=IllegalStateException.class)

Indica que la reversión no debería ocurrir si el método de destino genera esta excepción.

Ahora, el último pero más importante paso en la gestión de transacciones es publicar la anotación @Transactiona l. En la mayoría de los casos, surge la confusión donde se debe ubicar la anotación: ¿en el nivel de servicio o en el nivel DAO?

@Transactional : ¿Servicio o nivel DAO?

El servicio es el mejor lugar para colocar @Transactional , el nivel de servicio debe contener el comportamiento del caso de uso al nivel de detalle para la interacción del usuario, que lógicamente entra en la transacción.

Hay muchas aplicaciones CRUD que no tienen una lógica comercial significativa que tenga un nivel de servicio que simplemente transfiera datos entre controladores y objetos de acceso a datos, lo que no es útil. En estos casos, podemos poner la anotación de transacción en el nivel DAO.

Por lo tanto, en la práctica, puede colocarlos en cualquier lugar, depende de usted.

Además, si coloca @Transactional en la capa DAO y si su capa DAO será reutilizada por diferentes servicios, será difícil colocarla en la capa DAO, ya que diferentes servicios pueden tener diferentes requisitos.

Si su nivel de servicio recupera objetos usando Hibernate, y digamos que tiene inicializaciones diferidas en la definición de un objeto de dominio, entonces necesita abrir la transacción en el nivel de servicio, de lo contrario, encontrará una excepción LazyInitializationException lanzada por ORM.

Considere otro ejemplo donde su nivel de servicio puede llamar a dos métodos DAO diferentes para realizar operaciones de base de datos. Si su primera operación DAO falló, las otras dos pueden transferirse y finalizará el estado inconsistente de la base de datos. La anotación de nivel de servicio puede salvarlo de tales situaciones.

Espero que este artículo te haya ayudado.

El fin

Siempre es interesante ver sus comentarios o preguntas.

Source: https://habr.com/ru/post/es431508/


All Articles