¿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.
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:
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.
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.
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
}
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.
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. |
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.
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;
}
}