Transacciones en la capa de negocio

RECU-0170 (Recurso Referencia)

Descripción

Una de las razones más convincentes para utilizar el marco Spring es el apoyo de transacción global. El Marco de Spring proporciona una abstracción coherente para la gestión de transacciones que ofrece los siguientes beneficios.

  • Proporciona un modelo de programación consistente a través de las API de transacciones diferentes, tales como JTA, JDBC, Hibernate, JPA, y JDO.
  • Apoya la gestión de transacciones declarativa.
  • Proporciona una sencilla API para la gestión de transacciones programática de una serie de API de transacciones complejas tales como JTA.
  • Se integra muy bien con las abstracciones de acceso a datos.

Tradicionalmente, los desarrolladores de J2EE han tenido dos opciones para la gestión de transacciones: las transacciones globales o locales. Las transacciones globales son gestionados por el servidor de aplicaciones, utilizando el API de transacciones Java (JTA). Las transacciones locales son recursos específicos: el ejemplo más común sería una transacción asociados con una conexión JDBC. Esta elección tiene profundas implicaciones. Por ejemplo, las transacciones globales proporcionan la capacidad de trabajar con múltiples recursos transaccionales (bases de datos relacionales y normalmente las colas de mensajes). Con las transacciones locales, el servidor de aplicaciones no está involucrado en la gestión de transacciones y no puede ayudar a asegurar la corrección a través de múltiples recursos. (Cabe señalar que la mayoría de las aplicaciones de uso de un recurso sola transacción.)

Transacciones globales

Las transacciones globales tienen una desventaja importante, en ese código tiene que usar JTA, y JTA es una API difícil de utilizar (en parte debido a su modelo de excepción). Además, un UserTransaction JTA normalmente tiene que proceder de JNDI.Por lo tanto tenemos que utilizar tanto JNDI y JTA utilizar JTA. Obviamente, todo uso de las transacciones globales limita la reutilización de código de la aplicación, ya que JTA normalmente sólo está disponible en un entorno de servidor de aplicaciones. Anteriormente, la mejor forma de utilizar las transacciones globales fue a través de EJB CMT (transacción administrada por contenedor).

CMT es una forma de gestión de transacciones declarativa (a diferencia de la gestión de transacciones programática). EJB CMT elimina la necesidad de transacción relacionados con búsquedas JNDI, aunque por supuesto, el uso de EJB requiere el uso de JNDI. Se elimina la mayor parte de la necesidad (aunque no del todo) de escribir código Java para el control de las transacciones. El inconveniente importante es que la CMT está ligada a JTA y un entorno de servidor de aplicaciones. Además, sólo está disponible si se opta por aplicar la lógica de negocio en EJB, o al menos detrás de una fachada de EJB transaccional. Los negativo en torno a EJB, en general, que no se trata de una propuesta atractiva, especialmente en la cara de las alternativas de peso para la gestión de transacciones declarativa.

Transacciones locales

Las transacciones locales pueden ser más fáciles de usar, pero tiene desventajas importantes: no pueden trabajar a través de múltiples recursos transaccionales. Por ejemplo, el código que gestiona las transacciones utilizando una conexión de JDBC no se puede ejecutar en una transacción JTA global. Otra desventaja es que las transacciones locales tienden a ser invasoras en el modelo de programación.

Spring resuelve estos problemas. Permite a los desarrolladores de aplicaciones para utilizar un modelo de programación coherente en cualquier entorno. Debe escribir el código una vez, y pueden beneficiarse de diferentes estrategias de manejo de transacciones en diferentes entornos. El Marco de Spring proporciona la gestión de transacciones declarativas y programáticas. La gestión de transacciones declarativa es la preferida por la mayoría de los usuarios, y se recomienda en la mayoría de los casos.

Con la gestión de transacciones programática, los desarrolladores trabajan con la abstracción de la transacción Spring Framework, que puede correr sobre cualquier infraestructura de operación subyacente. Con el modelo de declaración preferido, los desarrolladores suelen escribir poco o ningún código relacionado con la gestión de transacciones, y por lo tanto no dependen de la transacción de Spring Framework.

Estrategia de transacción

La clave para la captación de transacciones de Spring es la noción de una estrategia de operación. Una estrategia de operación se define por la interfaz de org.springframework.transaction.PlatformTransactionManager, que se muestra a continuación:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition)
        throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

Tengamos en cuenta que de acuerdo con la filosofía del marco de Spring, PlatformTransactionManager es una interfaz, y por lo tanto puede ser fácilmente mockeada cuando sea necesario. Tampoco está vinculada a una estrategia de búsqueda como JNDI. Las implementaciones PlatformTransactionManager se definen como cualquier otro objeto (o bean) en el contenedor de Spring Framework COI. Este beneficio sólo lo hace una con un objetivo de abstracción. Incluso cuando se trabaja con JTA, los códigos de transacción pueden ser probados con mayor facilidad que si se utiliza directamente JTA.

Una vez más en consonancia con la filosofía de Spring, el TransactionException que puede ser lanzado por cualquiera de los métodos de la interfaz de PlatformTransactionManager siendo unchecked (es decir, se extiende la clase java.lang.RuntimeException).

Fallos en la infraestructura de transacción son casi siempre definitivos. En pocos casos el código de aplicación puede recuperarse de un error de transacción, el desarrollador de aplicaciones aún puede elegir capturar y manejar TransactionException. El punto importante es que los desarrolladores no están obligados a hacerlo.

El getTransaction (..) el método devuelve un objeto TransactionStatus, en función de un parámetro TransactionDefinition. El TransactionStatus devuelto podría representar una operación nueva o ya existente (si hay una transacción coincidente en la pila de llamadas actual con la implicación de que (como en contextos de transacciones J2EE) un TransactionStatus se asocia con un hilo de ejecución).

La interfaz de TransactionDefinition especifica:

  • Aislamiento: el grado de aislamiento de esta transacción sobre otras transacciones. Por ejemplo, ¿esta transacción puede verse comprometida por la escritura de otras transacciones?
  • Propagación: en general, todo código que se ejecuta dentro de un ámbito de transacción se ejecutará en esa transacción. Sin embargo, hay varias opciones que especifica el comportamiento si se ejecuta un método de transacción cuando el contexto de la transacción ya existe: por ejemplo, sólo tiene que seguir corriendo en la operación existente (el caso más común), o la suspensión de la operación existente y la creación de una nueva transacción. Spring ofrece todas las opciones de transacción de propagación familiares de EJB CMT.
  • Tiempo de espera: ¿cuánto tiempo tiene la transacción para ejecutarse antes de tiempo de espera (y automáticamente se deshace de la infraestructura de transacción subyacente).
  • Estado de sólo lectura: una transacción de lectura única no puede modificar ningún dato. Transacciones de sólo lectura puede ser una optimización de utilidad en algunos casos (como cuando se utiliza Hibernate).

La interfaz de TransactionStatus proporciona una forma sencilla para el código de transacción para controlar la ejecución de transacciones y estado de la transacción de consulta. Los conceptos deben estar familiarizados, ya que son comunes a todas las API de transacciones:

public interface TransactionStatus {

    boolean isNewTransaction();

    void setRollbackOnly();

    boolean isRollbackOnly();
}

Independientemente de si se opta por la gestión de transacciones declarativa o programática en Spring, la definición de PlatformTransactionManager correcta es absolutamente esencial. En Spring , esta importante definición generalmente se realiza mediante la inyección de dependencias. Las implementaciones PlatformTransactionManager normalmente requieren el conocimiento del entorno en el que trabajan: JDBC, JTA, Hibernate, etc

Transacciones declarativas

La mayoría de los usuarios de Spring eligen la gestión de transacciones declarativa. Es la opción con el menor impacto en el código de aplicación, y por lo tanto es más coherente con los ideales de un contenedor no invasivo de peso ligero.

La gestión de transacciones declarativa es posible con Spring AOP, aunque, como el código de transacción aspectos viene con la distribución de Spring Framework y puede ser utilizado de manera repetitivo, los conceptos de AOP en general, no han de entenderse de hacer un uso eficaz de este código.

Puede ser útil comenzar por considerar EJB CMT y explicar las similitudes y diferencias con la gestión de transacciones declarativa en el marco de Spring. El enfoque básico es similar: es posible especificar el comportamiento de la transacción (o falta de ella) hasta el nivel de método individual. Es posible hacer una setRollbackOnly () llamada dentro de un contexto de transacción si es necesario. Las diferencias son:

  • A diferencia de EJB CMT, que está vinculado a JTA, la gestión de transacciones declarativa Spring Framework funciona en cualquier entorno. Puede trabajar con JDBC, JDO, Hibernate u otras transacciones , con los cambios de configuración solamente.
  • El framework Spring permite la gestión de transacciones declarativa que deben aplicarse a cualquier clase, no sólo las clases especiales, tales como EJB.
  • Ofrece normas reversión declarativa: una característica que no tiene equivalente EJB. El retroceso se puede controlar de forma declarativa, no sólo mediante programación.
  • Spring da la oportunidad de personalizar el comportamiento transaccional, mediante AOP. Por ejemplo, si desea insertar un comportamiento personalizado en el caso del desmantelamiento de transacción, se puede. También se puedes añadir el asesoramiento arbitrarias, junto con el asesoramiento de transacciones. Con EJB CMT, no tiene manera de influir en la gestión de otra transacción del contenedor de setRollbackOnly ().
  • No es compatible con la propagación de los contextos de transacciones a través de las llamadas remotas, así como servidores de alta aplicación final. Si usted necesita esta característica, le recomendamos que utilice EJB. Sin embargo, tenga en cuenta cuidadosamente antes de utilizar esta característica. Normalmente, no queremos que las realicen las llamadas remotas

El concepto de las normas de reversión es importante ya que nos permiten especificar que excepciones deberían causar el rollback. Nos permite especificar esta declaración, en la configuración, no en el código Java

Rollback de las transacciones

Esta sección describe cómo usted puede controlar la vuelta atrás de las transacciones de manera declarativa simple. El método recomendado para indicar a la infraestructura de transacción de Spring que el trabajo de una transacción se revierte es una excepción de código que se ejecuta actualmente en el contexto de una transacción. La infraestructura Spring de transacción capturará cualquier excepción no controlada, y la insertara de la pila de llamadas, y será la marca para la vuelta atras.

Sin embargo, tenga en cuenta que la infraestructura del código de transacción, por defecto, sólo se marca una transacción para la reversión en el caso de tiempo de ejecución, las excepciones sin control, es decir, cuando la excepción que se es una instancia o subclase de RuntimeException. (Los errores también - por omisión - que se deshaga.) Las excepciones controladas que se lanzan desde un método de transacción no darán lugar a la vuelta atrás.

Exactamente qué tipo de excepción marca para deshacer una transacción se puede configurar.A continuación un fragmento de configuración XML que se muestra cómo se podría configurar de una reversión marcada, sobre un tipo de excepción específica.

<tx:advice id="txAdvice" transaction-manager="txManager">
  <tx:attributes>
     <tx:method name="get*" read-only="false" rollback-for="NoProductInStockException"/>
     <tx:method name="*"/>
  </tx:attributes>
</tx:advice>

Otra forma de indicar a la infraestructura de Transacción que se requiere una reversión es hacerlo mediante programación. Aunque es sencillo, de esta manera es bastante invasiva, y crea dependencias a la infraestructura de su código de transacción. Un ejemplo a continuación de un fragmento de código que hace rollback.

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

Configuración de <tx:advice/>

Los diferentes escenarios de una transacción que pueden ser especificados utilizando el <tx:advice/> etiqueta. El valor predeterminado <tx:advice/> es:

  • El ajuste de propagación es REQUIRED
  • El nivel de aislamiento es DEFAULT
  • La transacción es de lectura / escritura
  • Los valores predeterminados de transacción de tiempo de espera para el tiempo de espera predeterminado del sistema de operación subyacente, o ninguno, si los tiempos de espera no son compatibles
  • Cualquier RuntimeException activará la reversión, y cualquier excepción controlada no

Uso de @Transactional

Además del enfoque basado en XML declarativa a la configuración de la transacción, también puede utilizar un enfoque basado en la anotación en la configuración de la transacción. Declarar la semántica de transacciones directamente en el código fuente Java sitúa las declaraciones mucho más cerca del código afectado, y en general no hay mucho peligro de acoplamiento indebido, puesto que el código que está destinado a ser utilizado transaccionalmente es casi siempre desplegado así de todos modos.

La anotación @transaccional puede ser colocada antes de una definición de interfaz, un método en una interfaz, una definición de clase, o un método público en una clase. Sin embargo, tenga en cuenta que la mera presencia de la anotación @transaccional no es suficiente para convertir en realidad en el comportamiento transaccional. En este caso es necesaria la presencia del elemento par el funcionamiento de los interruptores en el comportamiento transaccional.

La recomendación es que sólo anotar clases concretas con la anotación @transaccional, en lugar de anotar las interfaces. Por supuesto que puede colocar la anotación @transaccional en una interfaz (o un método de interfaz), pero esto sólo funcionará correctamente si está utilizando una interfaz basada en proxies.

El hecho de que las anotaciones no se heredan significa que si usted está utilizando una clase basada en un proxy , entonces, la configuración de transacción no será reconocido por la clase proxy basado en la infraestructura y el objeto no se verá envuelto en un proxy transaccional (que sería un error) .

Configuración de @Transactional

La anotación @transaccional es un metadato que especifica que una interfaz, clase o método debe tener semántica transaccional. La configuración por defecto @transaccional es:

  • El ajuste de propagación es PROPAGATION_REQUIRED
  • El nivel de aislamiento es ISOLATION_DEFAULT
  • La transacción es de lectura / escritura
  • Los valores predeterminados de transacción de tiempo de espera para el tiempo de espera predeterminado del sistema de operación subyacente, o ninguno, si los tiempos de espera no son compatibles
  • Cualquier RuntimeException activará la reversión, y cualquier excepción controlada no

Ejemplos

Normalmente con el uso de las transacciones se busca mejorar el grado de integridad y consistencia de datos. Tomamos como punto de comienzo el uso de transacciones locales, también conocidas como transacciones de base de datos. Al comienzo de la persistencia en las bases de datos (JDBC), se solía delegar el procesamiento de las transacciones a la base de datos. Las transacciones de bases de datos trabajan bien para Unidades Lógicas de Trabajo que ejecutan una única sentencia insert, update o delete.Un ejemplo en JDBC

@Stateless
public class TradingServiceImpl implements TradingService {
   @Resource SessionContext ctx;
   @Resource(mappedName="java:jdbc/tradingDS") DataSource ds;
 
   public long insertTrade(TradeData trade) throws Exception {
      Connection dbConnection = ds.getConnection();
      try {
         Statement sql = dbConnection.createStatement();
         String stmt =
            "INSERT INTO TRADE (ACCT_ID, SIDE, SYMBOL, SHARES, PRICE, STATE)"
          + "VALUES ('"
          + trade.getAcct() + "','"
          + trade.getAction() + "','"
          + trade.getSymbol() + "',"
          + trade.getShares() + ","
          + trade.getPrice() + ",'"
          + trade.getState() + "')";
         sql.executeUpdate(stmt, Statement.RETURN_GENERATED_KEYS);
         ResultSet rs = sql.getGeneratedKeys();
         if (rs.next()) {
            return rs.getBigDecimal(1).longValue();
         } else {
            throw new Exception("Trade Order Insert Failed");
         }
      } finally {
         if (dbConnection != null) dbConnection.close();
      }
   }
}

El código de ejemplo no contiene ninguna lógica de transacción, y persiste una orden de compra de acciones en la tabla TRADE de la base de datos. En este caso, la base de datos se encarga de manejar la lógica transaccional.

Tras la llegada de estándares y frameworks de persistencia Java como Hibernate, TopLink y Java Persistence API (JPA), es bastante raro escribir código JDBC directamente. Generalmente se usan los frameworks de mapeo objeto-relacional (ORM) para realizar el esfuerzo de forma sencilla y reemplazar todo el código JDBC por unas simples invocaciones. Continuando con el ejemplo anterior, para insertar una orden de compra del ejemplo anterior en JDBC, usando Spring Framework con JPA, mapeamos el objeto TradeData a la tabla TRADE y reemplazamos todo el código JDBC con el código JPA:

public class TradingServiceImpl {
    @PersistenceContext(unitName="trading") EntityManager em;
 
    public long insertTrade(TradeData trade) throws Exception {
       em.persist(trade);
       return trade.getTradeId();
    }
}

Si pensamos que tras la ejecución del código se realizará la orden o lanzará una excepción estamos equivocados. Simplemente va a retornar el valor 0 como la clave de la orden de compra sin cambiar la base de datos.

Se observa una de las primeras necesidades en el procesamiento de transacciones. Para realizar operaciones que necesiten de la sincronización de los objetos integrados en la cache y la base de datos es necesario integrar transacciones. Cuando se lelva a cabo un commit, el código SQL se genera y se realiza la acción dentro de la base de datos (siguiendo el modelo CRUD). Si no existe transacción, eL ORM no es capaz de interpretar la acción ya que no recibe ningún tipo de señal o disparador y por lo tanto ni genera el SQL ni persiste los cambios Si se usa un framework ORM, debemos usar transacciones. Ya no podemos confiar en que la base de datos va a manejar las conexiones y hacer el commit del trabajo.

Problemas asociados al tratamiento de la anotación @Transactional en Spring

Spring Framework tiene una anotación @Transactional para indicar el tratamiento de transacciones. Si la añadimos al código de ejemplo tendremos:

public class TradingServiceImpl {
   @PersistenceContext(unitName="trading") EntityManager em;
 
   @Transactional
   public long insertTrade(TradeData trade) throws Exception {
      em.persist(trade);
      return trade.getTradeId();
   }
}

Si realizamos la gestión de las transacciones mediante el framework de Spring necesitamos comunicarle a Spring que estamos usando anotaciones indicándole quién es el que maneja la transacción. Este error suele ser difícil de descubrir, y hace que los desarrolladores terminen añadiendo la lógica de transacciones en los archivos de configuración de Spring en vez de usar anotaciones.

MADEJA recomienda usar anotaciones para el uso de transacciones

Cuando se usa la anotación @Transactional de Spring, debemos agregar la línea siguiente a nuestro archivo de configuración de Spring:

<tx:annotation-driven transaction-manager="transactionManager" />

La propiedad transaction-manager tiene una referencia al bean que gestiona las transacciones, definido en el archivo de configuración de Spring. Este código le dice a Spring que use la anotación @Transactional cuando aplique el interceptor de transacciones.

MADEJA recomienda evitar el uso de la anotación @Transactional cuando hacemos operaciones de sólo lectura, debido a los problemas que pueden derivarse de la interpretación por parte de los frameworks

Problemas de uso empleando el atributo REQUIRES_NEW

Si se usa el atributo de transacción REQUIRES_NEW siempre inicia una transacción nueva cuando comienza el método, exista o no una transacción en curso. Con frecuencias se usa REQUIRES_NEW de manera incorrecta, asumiendo que es la forma correcta de asegurar el inicio de transacciones

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {...}
 
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void updateAcct(TradeData trade) throws Exception {...}

Ambos métodos son públicos, por lo que pueden ser invocados de forma independiente uno del otro. Los problemas surgen con el atributo REQUIRES_NEW cuando hay métodos que los usan dentro de la misma Unidad Lógica de Trabajo a través de comunicación entre servicios, o usando orquestación. Por ejemplo, supongamos que invocamos al método updateAcct() de forma independiente de cualquier otro método en algunos casos de uso, pero también hay un caso donde el método se invoca dentro del método insertTrade(). Ahora bien, si ocurre una excepción luego de la invocación a updateAcct(), la orden de compra va a tener un rollback, pero se va a realizar un commit de la actualización a la cuenta. Por ejemplo:

@Transactional(propagation=Propagation.REQUIRES_NEW)
public long insertTrade(TradeData trade) throws Exception {
   em.persist(trade);
   updateAcct(trade);
   //Una excepción ocurre acá! La orden de compra tiene un rollback,
   // pero la actualización de la cuenta no!
   ...
}
El atributo de transacción REQUIRES_NEW sólo debe usarse si la acción sobre la base de datos necesita guardarse sin importar el resultado de la transacción subyacente.