## Control de Transaciones

¿No sería maravilloso si cada operación que intentara nuestra aplicación tuviera éxito? Desafortunadamente, en el mundo multi-thread de las aplicaciones distribuidas y recursos compartidos, esto no es siempre posible.

¿Por qué? Primero de todo, los recursos compartidos deben mantener una vista consistente de los datos de todos los usuarios. Esto significa que leer y escribir tiene que ser controlado para que los usuarios no se sobreescriban los datos unos a los otros, o los errores de transación no corrompan la integridad de los datos. También, si trabajamos en una red con retardos intermitenes o caídas de conexiones, el potencial para que las operaciones fallen en una aplicación basada en web se incrementa con el número de usuarios.

Los fallos de operaciones son inevitables, lo mejor es recuperar luego la seguridad, y aquí es donde entra el control de transaciones. Las bases de datos modernas y los controladores de transaciones nos permiten deshacer y repetir el estado de una secuencia de operaciones fallidas para asegurar que los datos son consistentes para el acceso desde múltiples threads.

Esta sección añade código al SellerBean del ejemplo de la casa de subastas para que pueda manejar la inserción de itéms en la subasta más allá del controlador de transaciones por defecto proporcionado por su contenedor.

- ¿Por qué Controlar las Transaciones?
- Sincronización de Sesión
  - Ejemplo de Contenedor Controlador
  - Código
- Modo de Entrega de la Transación
  - Configurador del Servidor
  - Descripciones de Atributos de Transación
  - Descripciones del Nivel de Aislamiento
  - Ejemplo de Bean-Controlador

### ¿Por qué Controlar las Transaciones?

Cuando accedemos a una base de datos usando el API JDBC, todas las aplicaciones se ejecutan con una entrega automática explícita por defecto. Esto significa que cualquier aplicación que esté viendo los datos verá los datos actualizados después de cada llamada a JDBC.

Para aplicaciones sencillas, esto podría ser aceptable, pero consideremos la aplicación de la casa de subastas y las secuencias que ocurren cuando SellerBean inserta un ítem de subasta. Primero se carga la cuenta del usuario para listar el ítem, y se añade el ítem a la lista de ítems de la subasta. Estas operaciones ivolucran a RegistrationBean para cobrar la cuenta y AuctionItemBean para añadir el ítem a la lista de subasta.

En el modo de entrega automático, si falla la inserción del ítem de subasta, sólo se se puede deshacer el listado, y tenemos que ajustar manualmente la cuenta del usuario para descontarle la lista de cargos. Mientras tanto, otro thread podría estar intentando deducir de la misma cuenta de usuario, sin encontrar crédito, y abortando cuando quizás unos milisegundos después se podría haber completado.

Hay dos formas para segurarnos de que el débito se ha devuelto a su valor cuando falla la inserción de un ítem en la subasta:

- Añadir código de sincronización de sesión al Bean de sesión del contenedor controlador para obtener el control sobre las entregas de la transación y volver atrás.

- Configurar JDBC para los servicios de modo de entrega transación y añadir código para arrancar, parar, entregar y deshacer la transación. Esto es una transación controlada por Bean y puede ser usada por Beans de entidad y de sesión.

### Sincronización de Sesisón

Un Bean de sesión controlado por contenedor puede opcionalmente incluir sincronización de sesión para controlar la entrega automática por defecto propocionada por el contenedor. El código de sincronización permite al contenedor notificar al Bean cuando se alcanzan los puntos importantes en la transación. Además de recibir la notificación, el Bean puede tomar cualquier acción necesaria antes de que la transación proceda con el siguiente punto.

> Nota: Un Bean de Sesión que usa transaciones controladas por Bean no necesita sincronización de sesión porque tiene la entrega totalmente controlada.

#### Ejemplo de Control por Contenedor

SellerBean es un Bean de sesión que usa RegistrationBean para comprobar la ID y la password del usuario cuando alguien postea un ítem para la subasta y apunta en la cuenta del vendedor un listado, y AuctionItemBean añade los nuevos ítems a la base de datos.

La transación empieza en el método insertItem con el apunte del débito y termina cuando se entrega la transación completa o se deshace. La transación completa incluye deshacer el apunte de 50 centavos si el ítem de subasta es null (la inserción falla), o si se captura una excepción. Si el ítem de subasta no es null y la inserción se realiza con éxito, se entrega la transación completa, incluyendo el cobro de los 50 centavos.

#### Código

Para usar sincronización de sesión, un Bean de sesión implementa el interface SessionSynchronzation y sus tres métodos, afterBegin, beforeCompletion, y afterCompletion. Este ejemplo adapta el código de SellerBean.java para usar sincronización de sesión.

>     public class SellerBean implements SessionBean,
>     			SessionSynchronization {

>       private transient SessionContext ctx;
>       private transient Properties p = new Properties();
>       private transient boolean success = true;

>       public void afterBegin() {}

>       public void beforeCompletion() {
>         if (!success ) {
>           ctx.setRollbackOnly();
>         }
>       }

>       public void afterCompletion(boolean state) {}

afterBegin: El contenedor llama a este método antes del débito para notificar al Bean de sesión de que una nueva transación va a comenzar. Podemos implementar este método que haga cualquier trabajo prévio en la base de datos que pudiera ser necesario para la transación. En este caso no son necesarios trabajos prévios, por eso este método no está implementado.

beforeCompletion: El contenedor llama a este método cuando está listo para escribir el ítem de subasta y el débito en la base de datos, pero antes de hacerlo realmente (entregarlo). Podemos implementar este método para escribir cualquier actualización caché de la base de datos o deshacer la transación. En este ejemplo, el método llama al método setRollbackOnly sobre el contexto de la sesión en el caso de que la variable success sea false durante la transación.

afterCompletion: El contenedor llama a este método cuando la transación se entrega. Un valor booleano de true significa que el dato ha sido enviado y false significa que la transación se ha deshecho. El método usa el valor boolean para determinar si necesita resetear el estado del Bean en el caso de que se haya deshecho. En este ejemplo, no es necesario resetear el estado en el caso de un fallo.

Aquí está el método insertItem con comentarios que muestran dónde están los puntos donde se llama a los métodos de SessionSynchronization.

>     public int insertItem(String seller,
>     		      String password,
>     		      String description,
>     		      int auctiondays,
>     		      double startprice,
>     		      String summary)
>     		throws RemoteException {

>       try{
>         Context jndiCtx = new InitialContext(p);

>         RegistrationHome rhome =
>     	(RegistrationHome) sCtx.lookup("registration");
>         RegistrationPK rpk=new RegistrationPK();
>         rpk.theuser=seller;
>         Registration newseller=rhome.findByPrimaryKey(rpk);

>         if((newseller == null) ||
>     	(!newseller.verifyPassword(password))) {
>           return(Auction.INVALID_USER);
>         }

>     //Call to afterBegin
>         newseller.adjustAccount(-0.50);

>         AuctionItemHome home = (AuctionItemHome)
>     	jndiCtx.lookup("auctionitems");
>         AuctionItem ai= home.create(seller,
>     				description,
>     				auctiondays,
>     				startprice,
>     				summary);
>         if(ai == null) {
>           success=false;
>           return Auction.INVALID_ITEM;
>         }
>         else {
>           return(ai.getId());
>         }

>         }catch(Exception e){
>           System.out.println("insert problem="+e);
>           success=false;
>           return Auction.INVALID_ITEM;
>         }
>     //Call to beforeCompletion
>     //Call to afterCompletion

>      }

### Modo de Entrega de la Transación

Si configuramos los servicos JDBC para modo de entrega de transación, podemos hacer que el Bean controle la transación. Para configurar los servicios de JDBC para la entrega, llamamos a con.setAutoCommit(false) sobre nuestra conexión JDBC. No todos los drivers JDBC soportan el modo de entrega, pero para hacer que el Bean controle y maneje las transaciones, necesitamos un driver que lo haga.

El modo de entrega de la transación nos permite añadir código que crea una red de seguridad alrededor de una secuencia de operaciones dependientes. El API de Transaction de Java, proporciona las ayudas que necesitamos para crear esa red de seguridad. Pero si estamos usando la arquitectura JavaBeans de Enterprise, podemos hacerlo con mucho menos código. Sólo tenemos que configurar el servidor de JavaBeans de Entrprise, y especificar en nuestro código donde empieza la transación, donde para, donde se deshace y se entrega.

#### Configuración del servidor

Configurar el servidor de JavaBeans Enterprise implica especificar las siguientes selecciones en un fichero de configuración para cada Bean:

- Un nivel de aislamiento para especificar cómo de exclusivo es el acceso de una transasción a los datos compartidos.

- Un atributo de transación para especificar cómo controlar las transaciones mediante el Bean o el contenedor que continúa en otro Bean.

- Un tipo de transación para especificar si la transación es manejada por el contenedor o por el Bean.

Por ejemplo, podríamos especificar estas selecciones para el servidor BEA Weblogic en un fichero DeploymentDescriptor.txt para cada Bean.

Aquí está la parte de DeploymentDescriptor.txt para SellerBean que especifica el nivel de aislamiento y el atributo de transación.

>     (controlDescriptors
>       (DEFAULT
>          isolationLevel            TRANSACTION_SERIALIZABLE
>          transactionAttribute      REQUIRED
>          runAsMode                 CLIENT_IDENTITY
>          runAsIdentity             guest
>       ); end DEFAULT
>     ); end controlDescriptors

Aquí está el equivalente en lenguaje XML.

>     <container-transaction>
>         <method>
>             <ejb-name>SellerBean<ejb-name>
>             <method-name>*<method-name>
>         <method>
>         <transaction-type>Container<transaction-type>
>         <trans-attribute>Required<trans-attribute>
>     <container-transaction>

En este ejemplo, SellerBean está controlado por el Bean.

>     <container-transaction>
>         <method>
>             <ejb-name>SellerBean<ejb-name>
>             <method-name>*<method-name>
>         <method>
>         <transaction-type>Bean<transaction-type>
>         <trans-attribute>Required<trans-attribute>
>     <container-transaction>

Descripción de Atributo de Transación: Un Bean Enterprise usa un transaction attribute para especificar si una transación de Bean es manejada por el propio Bean o por el contenedor, y cómo manejar las transaciones que empezaron en otro Bean.

El servidor de JavaBeans de Enterprise sólo puede controlar una transación a la vez. Este modelo sigue el ejemplo configurado por el Object Transaction Service (OTS) de la OMG, y significa que la especificación actual de los JavaBeans Enterpirse no proporcionan una forma para transaciones anidadas. Una transación anidada es un nueva transación que arranca dentro de otra transación existente. Mientras que las transaciones anidadas no están permitidas, continuar una transación existente en otro Bean es correcto.

Cuando se entra en un Bean, el servidor crea un contexto de transación para controlar la transación. Cuando la transación es manejada por le Bean, accedemos para comenzar, entregar o deshacer la transación cuando sea necesario.

Aquí están los atributos de transación con una breve descripción de cada uno de ellos. Los nombres de los atributos cambiaron entre las especificaciones 1.0 y 1.1 de los JavaBeans Enterprise.

Especificación 1. | Especificación 1.0
--- | ---

REQUIRED | TX_REQUIRED

Transación controlada por el contenedor. El servidor arranca y maneja una nueva transación a petición del usuario o continúa usando la transación que se arrancó en el código que llamó a este Bean.

REQUIRESNEW | TX_REQUIRED_NEW

Transación controlada por contenedor. El servidor arranca y maneja una nueva transación. Si una transación existente arranca esta transación, la suspende hasta que la transación se complete.

Especificado como tipo de transación de Bean en el Descriptor de desarrollo | TX_BEAN_MANAGED

<Transación controlada por el Bean. Tenemos acceso al contexto de la transación para empezar, entregar o deshacer la transación cuando sea necesario.

SUPPORTS | TX_SUPPORTS

Si el código que llama a este Bean tiene un transación en ejecución, incluye este Bean en esa transación.

NEVER | TX_NOT_SUPPORTED

Si el código que llama a un método en este Bean tiene una transación ejecuntándose, suspende esa transación hasta que la llamada al método de este Bean se complete. No se crea contexto de transación para este Bean.

MANDATORY | TX_MANDATORY

El atributo de transación para este Bean se configura cuando otro bean llama a uno de sus métodos. En este caso, este bean obtiene el atributo de transación del Bean llamante. Si el Bean llamante no tiene atributo de transación, el método llamado en este Bean lanza una excepcioón TransactionRequired.

Descripción del Nivel de Aislamiento: Un Bean de Enterprise usa un nivel de aislamiento para negociar su propia interacción con los datos compartidos y la interacción de otros threads con los mismos datos compartidos. Como el nombre indica, existen varios niveles de aislamiento con TRANSACTION_SERIALIZABLE siendo el nivel más alto de integridad de los datos.

> Nota:Debemos asegurarnos de que nuestra base de datos puede soportar el nivel elegido. En la especificación 1.1 de los JavaBeans de Enterprise, sólo los Beans de sesión con persistencia controlada por el Bean pueden seleccionar el nivel de aislamiento.

> Si la base de datos no puede controlar el nivel de aislamiento, el servidor de JavaBeans Enterprise dará un fallo cuando intente acceder al método setTransactionIsolation de JDBC.

TRANSACTION_SERIALIZABLE: Este nivel proporciona la máxima integridad de los datos. El Bean decide la cantidad de accesos exclusivos. Ninguna otra transación puede leer o escribir estos datos hasta que la transación serializable se complete.

En este contexto, serializable significa proceso como una operación serial, y no debería confundirse con la serialización de objetos para preservar y restaurar sus estados. Ejecutar transaciones como una sóla operación serial es la selección más lenta. Si el rendimiento es un problema, debemos usar otro nivel de aislamiento que cumpla con los requerimientos de nuestra aplicación, pero mejore el rendimiento.

TRANSACTION_REPEATABLE_READ: En este nivel, los datos leidos por una transación pueden ser leidos, pero no modificados, por otra transación. Se garantiza que el dato tenga el mismo valor que cuando fue leído por primera vez, a menos que la primera transación lo cambie y escriba de nuevo el valor cambiado.

TRANSACTION_READ_COMMITTED: En este nivel, los datos leídos por una transación no pueden ser leídos por otra transación hasta que la primera transación los haya entregado o deshecho

TRANSACTION_READ_UNCOMMITTED:En este nivel, los datos involucrados en una transación pueden ser leídos por otros threads antes de que la primera transación se haya completado o se haya deshecho. Las otras transaciones no pueden saber si los datos fueron finalmente entregados o deshechos.

#### Ejemplo de Bean Controlador

SellerBean es un Bean de sesión que usa RegistrationBean para chequear la ID y la password del usuario cuando alguien postea un ítem para la subasta, apunta el débito en la cuenta del usuario, y AuctionItemBean añade un nuevo ítem a la base de datos de la subasta.

La transación empieza en el método insertItem con el débito de la cuenta y termina cuando la transación completa se entrega o se deshace. La transación completa incluye deshacer el apunte de 50 centavos si el ítem de subasta es null (la inserción falla), o si se captura una excepción. Si el ítem de subasta no es null y la inserción se realiza con éxito, se entrega la transación completa, incluyendo el cobro de los 50 centavos.

Para este ejemplo, el nivel de aislamiento es TRANSACTION_SERIALIZABLE, y el atributo de transación es TX_BEAN_MANAGED. Los otros Beans en la transación, RegistrationBean y AuctionItemBean, tienen un nivel de aislamiento de TRANSACTION_SERIALIZABLE y un atributo de transación de REQUIRED.

Los cambios en esta versión de SellerBean sobre la versión del contenedor controlador se marcan con comentarios:

>     public int insertItem(String seller,
>     	String password,
>     	String description,
>     	int auctiondays,
>     	double startprice,
>     	String summary)
>     	throws RemoteException {

>     //Declare transaction context variable using the
>     //javax.transaction.UserTransaction class
>       UserTransaction uts= null;

>       try{
>         Context ectx = new InitialContext(p);

>     //Get the transaction context
>         uts=(UserTransaction)ctx.getUserTransaction();

>         RegistrationHome rhome = (
>     	RegistrationHome)ectx.lookup("registration");
>         RegistrationPK rpk=new RegistrationPK();
>         rpk.theuser=seller;
>         Registration newseller=
>     	rhome.findByPrimaryKey(rpk);

>         if((newseller == null)||
>     	(!newseller.verifyPassword(password))) {
>             return(Auction.INVALID_USER);
>         }

>     //Start the transaction
>         uts.begin();

>     //Deduct 50 cents from seller's account
>         newseller.adjustAccount(-0.50);

>         AuctionItemHome home = (
>     	AuctionItemHome) ectx.lookup("auctionitems");
>         AuctionItem ai= home.create(seller,
>     	description,
>     	auctiondays,
>     	startprice,
>     	summary);

>         if(ai == null) {
>     //Roll transaction back
>             uts.rollback();
>             return Auction.INVALID_ITEM;
>         }
>         else {
>     //Commit transaction
>             uts.commit();
>             return(ai.getId());
>         }

>         }catch(Exception e){
>             System.out.println("insert problem="+e);
>     //Roll transaction back if insert fails
>             uts.rollback();
>             return Auction.INVALID_ITEM;
>         }
>     }

Ozito