Manejo de Datos y Transaciones
Cuando usamos la arquitectura Enterprise JavaBeans, los datos se leen y escriben en la base de datos sin tener que escribir ningún código SQL. Pero ¿qué pasa si no quereos almacenar los datos en una base de datos, o si queremos escribir nuestros propios comandos SQL, o manejar transaciones?
Podemos sobreescribir el contenedor controlador interno de persistencia e implementar un Bean controlador de persistencia usando nuestro propio almacenamiento de datos y nuestro código de manejo de transaciones.
La persistencia del Bean controlador se convierte en útil cuando queremos más control del que proporciona el contenedor controlador. Por ejemplo podríamos sobreescribir la mayoría de los contenedores para que mapeen un Bean en una fila de la tabla, implementar nuestros propios métodos finder, o personalizar el caché.
Este capítulo presenta dos versiones de la clase RegistrationBean del capítulo anterior. Una versión lee y escribe los datos del usuario en un fichero usando streams de entrada y salida serializados. La otra versión proporciona nuestros propios comandos SQL para leer y escribir en la base de datos. También explica cómo podemos escribir nuestro propio código de manejo de transaciones.
Bean-Controlador de Persistencia y la Plataforma JDBC
Puede que haya algunas veces que querramos sobreescribir la persistencia del contenedor controlador e implementar métodos de Beans de entidad o de sesión para usar nuestros propios comandos SQL. Este tipo de persistencia controlada por el Bean puede ser útil si necesitamos aumentar el redimiento o mapear datos de múltiples Beans en una sóla fila de la tabla de la base de datos.
Esta sección nos muestra cómo convertir la clase RegistrationBean.java para acceder a la base de datos con la clase PreparedStatement del JDBC.
Conectar con la Base de Datos
Esta versión de la clase
RegistrationBean.java establece la conexión con la base de datos ejemplarizando una clase estática Driver y proporcionando el método getConnection.
El método getConnection necesita la clase estática DriverManager para un motor de la base datos registrada que corresponda con la URL. En este caso, la URL es weblogic.jdbc.jts.Driver.
//Create static instance of database driver
static {
new weblogic.jdbc.jts.Driver();
}
//Get registered driver from static instance
public Connection getConnection() throws SQLException{
return DriverManager.getConnection(
"jdbc:weblogic:jts:ejbPool");
}
Método Create
El método ejbCreate asigna valores a las variables miembro, obtiene una conexión con la base de datos, y crea un ejemplar de la clase java.sql.PreparedStatement para ejecutar la sentencia SQL que escribe los datos en la tabla registration de la base de datos.
Un objeto PreparedStatement se crea desde una sentenica SQL que se envía a la base de datos y se precompila antes de enviar cualquier dato. Podemos llamar a las sentencias setXXX apropiadas sobre el objeto PreparedStatement para enviar datos. Manteniendo los objetos PreparedStatement y Connection como variables de ejemplar privadas reducimos la sobrecarga porque las sentencias SQL no tienen que compilarse cada vez que se envían.
Lo último que hace el método ejbCreate es crear una clase de clave primaria con el ID del usuario, y devolverlo al contenedor.
public RegistrationPK ejbCreate(String theuser,
String password,
String emailaddress,
String creditcard)
throws CreateException, RemoteException {
this.theuser=theuser;
this.password=password;
this.emailaddress=emailaddress;
this.creditcard=creditcard;
this.balance=0;
try {
con=getConnection();
ps=con.prepareStatement("insert into registration (
theuser, password,
emailaddress, creditcard,
balance) values (
?, ?, ?, ?, ?)");
ps.setString(1, theuser);
ps.setString(2, password);
ps.setString(3, emailaddress);
ps.setString(4, creditcard);
ps.setDouble(5, balance);
if (ps.executeUpdate() != 1) {
throw new CreateException (
"JDBC did not create a row");
}
RegistrationPK primaryKey = new RegistrationPK();
primaryKey.theuser = theuser;
return primaryKey;
} catch (CreateException ce) {
throw ce;
} catch (SQLException sqe) {
throw new CreateException (sqe.getMessage());
} finally {
try {
ps.close();
} catch (Exception ignore) {}
try {
con.close();
} catch (Exception ignore) {}
}
}
Método Load
Este método obtiene la clave primaria desde el contexto de entidad y lo pasa al método refresh que carga los datos.
public void ejbLoad() throws RemoteException {
try {
refresh((RegistrationPK) ctx.getPrimaryKey());
}
catch (FinderException fe) {
throw new RemoteException (fe.getMessage());
}
}
Método Refresh
El método refresh es el código suministrado por el programador para cargar los datos desde la base de datos. Chequea la clave primaria, obtiene la conexión con la base de datos, y crea un objeto PreparedStatement para consultar en la base de datos la clave primaria especificada.
Los datos se leen desde la base de datos en un ResultSet y se asignan a las variables miembro globales para que RegistrationBean tenga la información más actualizada del usuario.
private void refresh(RegistrationPK pk)
throws FinderException, RemoteException {
if (pk == null) {
throw new RemoteException ("primary key
cannot be null");
}
Connection con = null;
PreparedStatement ps = null;
try {
con=getConnection();
ps=con.prepareStatement("select password,
emailaddress, creditcard,
balance from registration
where theuser = ?");
ps.setString(1, pk.theuser);
ps.executeQuery();
ResultSet rs = ps.getResultSet();
if (rs.next()) {
theuser = pk.theuser;
password = rs.getString(1);
emailaddress = rs.getString(2);
creditcard = rs.getString(3);
balance = rs.getDouble(4);
}
else {
throw new FinderException (
"Refresh: Registration ("
+ pk.theuser + ") not found");
}
}
catch (SQLException sqe) {
throw new RemoteException (sqe.getMessage());
}
finally {
try {
ps.close();
}
catch (Exception ignore) {}
try {
con.close();
}
catch (Exception ignore) {}
}
}
Método Store
Este método obtiene una conexión con la base de datos y crea un PreparedStatement para actualizarla.
public void ejbStore() throws RemoteException {
Connection con = null;
PreparedStatement ps = null;
try {
con = getConnection();
ps = con.prepareStatement("update registration
set password = ?,
emailaddress = ?,
creditcard = ?,
balance = ?
where theuser = ?");
ps.setString(1, password);
ps.setString(2, emailaddress);
ps.setString(3, creditcard);
ps.setDouble(4, balance);
ps.setString(5, theuser);
int i = ps.executeUpdate();
if (i == 0) {
throw new RemoteException (
"ejbStore: Registration (
" + theuser + ") not updated");
}
} catch (RemoteException re) {
throw re;
} catch (SQLException sqe) {
throw new RemoteException (sqe.getMessage());
} finally {
try {
ps.close();
} catch (Exception ignore) {}
try {
con.close();
}
catch (Exception ignore) {}
}
}
Método Find
El método ejbFindByPrimaryKey corresponde con la firma del método FindByPrimaryKey del interface
RegistrationHome. Este llama al método refresh para obtener o refrescar los datos de usuario para el usuario específicado en la clave primaria.
La versión de persistencia del contenedor controlador de RegistrationBean no implementa este método porque el contenedor maneja la obtención y refresco de los datos de usuario.
public RegistrationPK ejbFindByPrimaryKey(
RegistrationPK pk)
throws FinderException,
RemoteException {
if ((pk == null) || (pk.theuser == null)) {
throw new FinderException ("primary key
cannot be null");
}
refresh(pk);
return pk;
}
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?
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.INVALjdcbook_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.INVALjdcbook_ITEM;
}
else {
return(ai.getId());
}
}catch(Exception e){
System.out.println("insert problem="+e);
success=false;
return Auction.INVALjdcbook_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.1 |
Especificación 1.0 |
Explicación |
| 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.INVALjdcbook_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.INVALjdcbook_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.INVALjdcbook_ITEM;
}
}
Métodos de Búsqueda de Bean Controlador
La búsqueda en el contenedor controlador descrita en el capítulo 2 está basada en el mécanismo del método finder donde el descriptor de desarrollo, en lugar del Bean, especifica el comportamiento del método finder. Mientras el mecanismo del método finder funciona bien para consultas sencillas, no puede manejar operaciones complejas que impliquen más de un tipo de Bean o tablas de bases de datos. También, la especificación 1.1 de los JavaBeans de Enterprise actualmente no proporciona para poner las reglas del método finder en el descriptor de desarrollo.
Por eso, para consultas y búsquedas más complejas, tenemos que escribir búsquedas y consultas controladas por el Bean. Esta sección explica cómo escribir una versión de la facilidad de búsqueda de la casa de subastas controlada por el Bean. La búsqueda controlada por el Bean inplica cambios en el método AuctionServlet.searchItems y un nuevo Bean de sesión, SearchBean.
AuctionServlet.searchItems
La búsqueda empieza cuando el usuario final envía una cadena de búsqueda a la facilidad de búsqueda de la página principal de la casa de subastas, y pulsa el boton Submit. Esto llama a AuctionServlet, que recupera la cadena de búsqueda desde la cabecera HTTP y la pasa al método searchItem.
Nota: La lógica de búsqueda para este ejemplo es bastante simple. El propósito es mostrar cómo mover la lógica de búsqueda a otro Bean Enterprise separado para que podamos mover búsquedas más complejas nosotros solos.
La operación searchItem se hace en dos partes: 1) usar la cadena de búsqueda para recuperar las claves primarias, y 2) usar las claves primarias para recuperar los ítems de la subasta
Parte 1: Lo primero que hace el método searchItems es pasar la cadena de búsqueda enviada por el usuario final al Bean de sesión SearchBean.
SearchBean (descrito en la siguiente sección) implementa una búsqueda controlada por Bean que recupera una lista de claves primarias para todos los ítems de la subasta cuyo campo Summary contenga caracteres que correspondan con los de la cadena de búsqueda. Esta lista es devuelta al método searchItems en una variable Enumeration.
Enumeration enum=(Enumeration)
search.getMatchingItemsList(searchString);
Parte 2: El método searchItems usa la lista Enumeration devuelta en la parte 1 y usa AuctionItemBean para recuperar cada Bean por turno llamando a
findByPrimaryKey sobre cada clave primaria de la lista. Esta es una búsqueda controlada por contenedor basada en el mecanismo del método finder descrito en el capítulo 2.
//Iterate through search results
while ((enum != null) &&
enum.hasMoreElements())) {
while(enum.hasMoreElements(in)) {
//Locate auction items
AuctionItem ai=ahome.findByPrimaryKey((
AuctionItemPK)enum.nextElement());
displayLineItem(ai, out);
}
}
SearchBean
La clase SearchBean.java define una búsqueda controlada por el Bean para claves primarias de ítems de subasta con los campos summary que contienen caracteres que corresponden con la cadena de búsqueda. Este Bean establece una conexión con la base de datos, y proporciona los métodos getMatchingItemsList y
EJBCreate.
Conexión con la Base de Datos
Como este Bean controla su propio acceso a la base de datos, tiene que establecer su propia conexión con ella. No puede delegar esto al contenedor.
La conexión con la base de datos se establece ejemplarizando una clase Driver estática y proporcionando el método getConnection. Este método requiere una clase estática DriverManager para registrar un driver con la base de datos que corresponda con la URL. En este caso la URL es weblogic.jdbc.jts.Driver.
//Establish database connection
static {
new weblogic.jdbc.jts.Driver();
}
public Connection getConnection()
throws SQLException {
return DriverManager.getConnection(
"jdbc:weblogic:jts:ejbPool");
}
Obtener la Lista de Ítems Encontrados
El método getMatchingItemsList busca AuctionItemsBean y crea un objeto PreparedStatement para hacer una consulta a la base de datos por los campos summary que contengan la cadena de búsqueda. Los datos se leen desde la base de datos dentro de un ResultSet, almacenado en un Vector, y devuelto a AuctionServlet.
public Enumeration getMatchingItemsList(
String searchString)
throws RemoteException {
ResultSet rs = null;
PreparedStatement ps = null;
Vector v = new Vector();
Connection con = null;
try{
//Get database connection
con=getConnection();
//Create a prepared statement for database query
ps=con.prepareStatement("select id from
auctionitems where summary like ?");
ps.setString(1, "%"+searchString+"%");
//Execute database query
ps.executeQuery();
//Get results set
rs = ps.getResultSet();
//Get information from results set
AuctionItemPK pk;
while (rs.next()) {
pk = new AuctionItemPK();
pk.id = (int)rs.getInt(1);
//Store retrieved data in vector
v.addElement(pk);
}
rs.close();
return v.elements();
}catch (Exception e) {
System.out.println("getMatchingItemsList:
"+e);
return null;
}finally {
try {
if(rs != null) {
rs.close();
}
if(ps != null) {
ps.close();
}
if(con != null) {
con.close();
}
} catch (Exception ignore) {}
}
}
Método Create
El método ejbCreate crea un objeto javax.naming.InitialContext. Esta es una clase JNDI (Java Nanimg and Directory) que permite a SearchBean acceder a la base de datos sin relacionarse con el contenedor:
public void ejbCreate() throws CreateException,
RemoteException {
Properties p = new Properties();
p.put(Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.TengahInitialContextFactory");
try{
ctx = new InitialContext(p);
}catch(Exception e) {
System.out.println("create exception: "+e);
}
}