En la sesión pasada realizamos una introducción a las relaciones entre beans de entidad. En esta sesión vamos a detallar más cómo definir los distintos posibles tipos de relaciones, especificando para cada una de las siete posibles relaciones los distintos elementos que hay que definir para cada EJB que interviene en la relación:
Recordemos que los siete posibles tipos de relación son:
En esta sesión vamos a completar el ejemplo que comenzamos en la sesión anterior, la relación uno-a-uno unidireccional, añadiendo el esquema abstracto de persistencia, y vamos a definir los elementos necesarios para una relación uno-a-muchos unidireccional. De esta forma habremos revisado dos de los siete posibles tipos de relación.
EL modelo abstracto de programación define los métodos de negocio que están asociados a una relación entre EJBs. Por ejemplo, supongamos una relación uno-a-uno unidireccional entre Cliente y Direccion. Cada cliente tiene una dirección asoaciada. Para poder usar esta relación necesitaremos dos métodos de negocio: uno para definir el bean Direccion asociado a un Cliente y otro para obtenerlo. O supongamos una relación uno-a-muchos unidreccional entre Cliente y NumeroTelefono. Necesitaremos también una pareja de métodos de negocio para, dado un cliente, obtener o añadir números de teléfono asociados. La signatura de los métodos de negocio va a depender del tipo de relación que se haya definido.
Debido a la necesiadad de que las relaciones se implementen de forma eficiente, los métodos de negocio van a usar siempre interfaces locales de los EJB asociados. Es necesario por ello que todos los EJB que participan en una relación residan en el mismo contenedor EJB.
Los métodos de negocio deben definirse en la interfaz remota (y/o local) del EJB origen de la relación, además de en el fichero de implementación del EJB, donde deben declararse como abstractos.
Además de declarar métodos de acceso abstractos, el desarrollador del bean debe describir la cardinalidad y la dirección de las relaciones entre beans en el fichero de descripción del despliegue. Llamaremos esquema abstracto de persistencia a los elementos XML del fichero de descripción del despliegue que describen la relación.
Un esquema abstracto de persistencia de un bean se define en la sección <relationships> del descriptor XML del despliegue del bean. Dentro del elemento <relationships>, cada relación entidad-a-entidad se define en un elemento separado <ejb-relation>:
<ejb-jar> <enterprise-beans> ... </enterprise-beans> <relationships> <ejb-relation> ... </ejb-relation> <ejb-relation> ... </ejb-relation> </relationships> <assembly-descriptor> ... </assembly-descriptor> </ejb-jar>
Los elementos <ejb-relation> complementan el modelo abstracto de programación. Para cada par de métodos de acceso abstractos que definen un compo de relación, hay un elemento <ejb-relation> en el descriptor del despliegue. Además, todos los beans de entidad que participan en una relación se deben definir en el mismo descriptor de despliegue XML.
A continuación se lista parcialmente el descriptor de delspliegue de los EJBs Customer y Addres, haciendo énfasis en los elementos que definen la relación:
<ejb-jar> ... <enterprise-beans> <entity> <ejb-name>CustomerEJB</ejb-name> <local-home>.CusomterHomeLocal</local-home> <local>CustomerLocal</local> ... </entity> <entity> <ejb-name>AddressEJB</ejb-name> <local-home>AddressHomeLocal</local-home> <local>AddressLocal</local> ... </entity> ... </enterprise-beans> <relationships> <ejb-relation> <ejb-relation-name>Customer-Address</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-an-Address </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Address-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>AddressEJB</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships> </ejb-jar>
Todas las relaciones entre el EJB Customer y otros beans, como CreditCard, Address y Phone, requieren que definamos un elemento <ejb-relationZ para complementar los métodos abstractos de acceso.
Cada relación puede tener un nombre, que se declara en el elemento <ejb-relation-name>. Esto sirve para identificar la relación para las herramientas de despliegue, pero no es obligatorio.
Cada elemento <ejb-relacion> tiene exactamente dos elementos <ejb-relationship-role>, uno por cada participante en la relación. En el ejemplo anterior, el primer <ejb-relationship-role> declara el papel del EJB Customer en la relación. Sabemos esto porque el elemento <relationship-role-source> especifica el nombre del bean <ejb-name> como CustomerEJB, el nombre del bean Customer tal y como aparece en la sección <enterprise-beans>.
El elemento <ejb-relationship-role> también declara la cardinalidad del papel. El elemento <multiplicity> puede ser o bien One o Many. En este caso, el elemento <multiplicity> del EJB Customer tiene un valor de One, lo que quiere decir que cada EJB Address tiene una relación con exactamente un EJB Customer. El elemento <multiplicity> del EJB Address también señala One, lo que significa que cada EJB Customer tiene una relación con exactamente un EJB Address. Si el EJB Customer tuviera una relación con muchos EJBs Address, el elemento <multiplicity> del EJB Adress debería definirse como Many.
En la sesión anterior definimos métodos abstractos de acceso en el EJB Customer para obtener y definir el EJB Address en el campo address, pero el EJB Address no tenía métodos abstractos de acceso para el EJB Customer. En este caso estamos definiendo una relación unidireccional, lo que significa que sólo uno de los beans de la relación mantiene un campo de relación gestionado por el contenedor.
Si el bean descrito por la relación <ejb-relationship-role> mantiene una referencia a otro bean en la relación, esa referencia debe decalararse como un campo de relación gestionado por el contenedor en el elemento <cmr-field>. Este elementon se declara bajo el elemento <ejb-relationship-role>:
<ejb-relationship-role> <ejb-relationship-role-name> Customer-has-an-Address </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>address</cmr-field-name> </cmr-field> </ejb-relationship-role>
La especificación EJB 2.0 requiere que el <cmr-field-name> comience con una letra minúscula. Para cada campo de relación definido debe haber una pareja de métodos de acceso en la clase bean. Un método de la pareja debe definirse con el nombre set<cmr-field-name>(), con la primera letra del <cmr-field-name> cambiada a mayúscula. El otro método se define como get<cmr-field-name>(), también con la primera letra del <cmr-field-name> cambiada a mayúsculas.
En el ejemplo previo, el campo <cmr-field-name> es addres, con lo que se deben definir los métodos abstractos getAddress() y setAddress:
// bean class code public abstract void setAddress(AddressLocal address); public abstract AddressLocal getAddress(); // XML deployment descriptor declaration <cmr-field> <cmr-field-name>address</cmr-field-name> </cmr-field>
El tipo devuelto pr el método get<cmr-field-name() y el tipo del parámetro del método set<cmr-field-name>() deben ser el mismo. El tipo debe ser la interfaz local de bean de entidad al que se hace referencia o uno de los dos tipos java.util.Collection. En el caso del campo de relación address, usamos la interfaz local del EJB Address, AddressLocal. Los tipos Collection se usan cuando aparece la cardinalidad Many en la relación. Los veremos más adelante.
Es importante hacer notar de nuevo que aunque los beans de entidad pueden tener tanto interfaces locales como remotas, una relación gestionada por el contenedor puede usar sólo las interfaces locales del bean cuando hace persistente una relación. Por ello, por ejemplo, es ilegal definir un método abstracto de acceso que tenga un argumento de tipo javax.ejb.EJBObject (un tipo de interfaz remoto). Todas las relaciones gestionadas por el contenedor se basan en tipos javax.ejb.EJBLocalObject (interfaz local).
A lo largo de este tema se muestran distintos esquemas de tablas de bases de datos. La intención de estos esquemas es sólo demostrar posibles relaciones entre entidades en la base de datos; no son obligatorios. Por ejemplo, la relación Dirección-Cliente se pone de manifiesto introduciendo en la tabla CUSTOMER una clave foránea a la tabla ADDRESS. Esta no es la forma habitual de organización de la mayoría de bases de datos. En lugar de esto, probablemente usan una tabla de enlace o hacen que la tabla ADDRESS mantenga una relación foránea a CUSTOMER. Sin embargo, este esquema muestra cómo la persistencia gestionada por el contenedor puede soportar diferentes organizaciones de la base de datos.
A lo largo de este tema, suponemos que las tablas de las bases de datos se crean antes de la aplicación EJB. Algunos fabricantes ofrecen herramientas que generan las tablas automáticamente a paritr de las relaciones definidas entre los bean de entidad. Estas herramientas pueden crear esquemas que son muy distintos de los que se muestran aquí. En otros casos, los fabricantes que soportan esquemas de bases de datos ya establecidos pueden no tener la flexibilidad necesaria para soportar los esquemas ilustrados en este tema. Como un desarrollador EJB, debes ser lo suficientemente flexible para adaptarte a las facilidades proporcionadas por tu fabricante de EJB.
Un ejemplo de una relación uno-a-uno es la relación entre el EJB Customer y el EJB Address que vimos en el tema pasado. En este caso, cada Customer tiene exactamente un Address y cada Address tiene exactamente un Customer. Qué bean referencia a qué otro determina la dirección de la relación. En este caso el Customer tiene una referencia al Address, pero no al contrario. Es una relación unidireccional porque sólo puedes ir del Customer al Address, y no en la otra dirección. En otras palabras, un EJB Address no tiene idea de a quién pertenece.
La relación uno-a-uno unidireccional usa un esquema de base de datos típico en el que una tabla contiene una clave foránea a otra tabla. En este caso, la tabla CUSTOMER contiene una clave foránea a la tabla ADDRESS, pero no al contrario. Esto permite que los registros de la tabla ADDRESS sean compartido por otras tablas. el hecho de que el esquema de base de datos no sea el mismo que el esquema abstracto de persistencia muestra que son independientes hasta cierto punto.
Como hemos visto en el tema pasado, los métodos abstractos de acceso se usan para definir campos de relación en la clase del bean. Cuando un bean de entidad mantiene una referencia a otro bean, define un par de métodos abstractos de acceso para modelar esa referencia. En relaciones unidireccionales, que pueden navegarse sólo en una dirección, sólo uno de los enterprise beans define estos métodos abstractos de acceso. En este caso, en la clase CustomerBean se definen los métodos getAddress()/setAddress() para acceder a los EJBs Address, pero no se define ningún método de este tipo en la clase AddressBean para acceder el EJB Customer.
Un EJB Address puede ser compartido entre campos de relación del mismo enterprise bean, pero no puede ser compartido entre distintos EJBs Customer. Si, por ejemplo, el EJB Customer definiera dos campos de relación, billingAddress and homeAddress, como relaciones uno-a-uno unidireccionales con el EJB Address, estos dos campos podría referenciar al mismo EJB Address:
public class CustomerBean implements javax.ejb.EntityBean { ... public void setAddress(String street,String city,String state,String zip) { ... address = addressHome.createAddress(street, city, state, zip); this.setHomeAddress(address); this.setBillingAddress(address); AddressLocal billAddr = this.getBillingAddress(); AddressLocal homeAddr = this.getHomeAddress(); if(billAddr.isIdentical(homeAddr)) // always true ... } ... }
Si en cualquier momento quisiéremos distingur el billingAddress del homAdderss, simplemente tendríamos que llamar al método set con un EJB distinto. Para poder implementar estas relaciones, habría que modificar la tabla CUSTOMER:
CREATE TABLE CUSTOMER ( ID INT PRIMARY KEY, LAST_NAME CHAR(20), FIRST_NAME CHAR(20), ADDRESS_ID INT, BILLING_ADDRESS_ID INT }
Tal y como se ha mencionado, no es posible compartir un mismo EJB Address entre dos EJBs distintos Customer. Si, por ejemplo, el EJB Address que está asignado al Customer A se asignara al Customer B, el Address se movería de un Customer a otro y el Customer A dejaría de estar relacionado con ningún EJB Address. Este efecto, en apariencia extraño, es sencillamente un resultado natural de la definición uno-a-uno de la relación.
Si el EJB Customer no tienen un EJB Address asociado, el método getAddress() devolverá null.
Hemos definido ya los elementos XML de la relación Customer-Address We defined the XML elements for the Customer-Address. El elemento <ejb-relation> que usábamos declaraba una relación uno-a-uno unidireccional. Si necesitáramos usar dos campos de relación con el EJB Address, los campos homeAddress y billingAddress, cada una de estas relaciones tendría que ser descrita en su propio elemento <ejb-relation>:
<relationships> <ejb-relation> <ejb-relation-name>Customer-HomeAddress</ejb-relation-name> <ejb-relationship-role> ... <cmr-field> <cmr-field-name>homeAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> ... </ejb-relationship-role> </ejb-relation> <ejb-relation> <ejb-relation-name>Customer-BillingAddress</ejb-relation-name> <ejb-relationship-role> ... <cmr-field> <cmr-field-name>billingAddress</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> ... </ejb-relationship-role> </ejb-relation> </relationships>
Podemos ahora ampliar nuestro EJB Customer para incluir una referencia a un EJB CreditCard. Además, queremos que el EJB CreditCard mantenga también la referencia con el Customer que lo tiene asociado. Esto parece una buena decisión de diseño, ya que habrá bastantes aplicaciones en las que necesitaremos, a partir de una instancia del EJB CreditCard, conocer cuál es el Customer que la posee. Debido a que las referencias se realizan en ambos sentidos, tendremos una relación uno-a-uno bidimensional
El EJB CreditCard tiene una tabla correspondiente en la base de datos, que se llama CREDIT_CARD. Necesitaremos, evidentemente, la clave foránea que liga los Customer con los CreditCard. Además, al ser una relación bidireccional, necesitarems también añadir una clave foránea en la tabla CREDIT_CARD hacia la tabla CUSTOMER::
CREATE TABLE CREDIT_CARD ( ID INT PRIMARY KEY NOT NULL, EXP_DATE DATE, NUMBER CHAR(20), NAME CHAR(40), ORGANIZATION CHAR(20), CUSTOMER_ID INT } CREATE TABLE CUSTOMER ( ID INT PRIMARY KEY, LAST_NAME CHAR(20), FIRST_NAME CHAR(20), ADDRESS_ID INT, CREDIT_CARD_ID INT )
También es posible establecer una relación bidireccional uno-a-uno a través de una tabla de enlace, en la que que cada columna de clave foránea debe ser única. Esto es conveniente cuando no quieres imponer relaciones sobre las tablas originales. Las tablas de enlace se usan también el las relaciones una-a-muchos y muchos-a-muchos, pero es importante recordar que el esquema de base de datos usado en estos ejemplos es puramente ilustrativo. El esquema abstracto de persistencia de un bean de entidad se puede corresponder con una diversidad de esquemas de bases de datos; los esquemas de base de datos usado en estos ejemplos son sólo una posibilidad.
Para modelar la relación entre los EJB Customer y CreditCard, tendremos que declarar un campo de relación llamado customer en la clase CreditCardBean:
public abstract class CreditCardBean extends javax.ejb.EntityBean { ... // relationship fields public abstract CustomerLocal getCustomer(); public abstract void setCustomer(CustomerLocal local); // persistence fields public abstract Integer getId(); public abstract void setId(Integer id); public abstract Date getExpirationDate(); public abstract void setExpirationDate(Date date); public abstract String getNumber(); public abstract void setNumber(String number); public abstract String getNameOnCard(); public abstract void setNameOnCard(String name); public abstract String getCreditOrganization(); public abstract void setCreditOrganization(String org); // standard callback methods ... }
En este caso, usamos la interfaz local del EJB Customer (asumiendo que se ha creado) debido a que los campos de relación requieren tipos de interfaces locales. La limitación de usar interfaces locales en lugar de interfaces remotas es que perdemos la trasparencia de la ubicación. Todos los beans de entidad deben estar ubicados en el mismo proceso o la misma Máquina Virtual Java (JVM).
También podemos añadir un conjunto de métodos abstractos de acceso en la clase CustomerBean para el campo de relación creditCard:
public class CustomerBean implements javax.ejb.EntityBean { ... public abstract void setCreditCard(CreditCardLocal card); public abstract CreditCardLocal getCreditCard(); ... }
Aunque está disponible el método setCustomer() en el CreditCardBean, no tenemos que establecer explícitamente la referencia al Customer en el EJB CreditCard. Cuando una referencia a un EJB CreditCard se pasa en el método setCreditCard() en la clase CustomerBean, el contenedor EJB establecerá automáticamente la relación customer en el EJB CreditCard para que apunte al EJB Customer:
public class CustomerBean implements javax.ejb.EntityBean { ... public void setCreditCard(Date exp, String numb, String name, String org) throws CreateException { ... card = creditCardHome.create(exp,numb,name,org); // el campo customer del EJB CreditCard se establece automaticamente this.setCreditCard(card); Customer customer = card.getCustomer(); if(customer.isIdentical(ejbContext.getEJBLocalObject()) // always true ... } ... }
Las reglas para compartir un bean individual en una relación una-a-una bidireccional son las mismas que las de una relación uno-a-uno unidireccional. Mientras que un EJB CreditCard puede compartirse entre campos de relación en el mismo EJB Customer, no puede compartirse entre diferentes EJBs Customer. La asignación del CreditCard del Customer A al Customer B elimina la asociación entre el CreditCard y el Customer A y la mueve al Customer B.
El elemento <ejb-relation> que define la relación Customer-a-CreditCard es similar al usado para la relación Customer-a-Address, con una diferencia impoertante: los dos elementos <ejb-relationship-role> tienen un <cmr-field>:
<relationships> <ejb-relation> <ejb-relation-name>Customer-CreditCard</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-a-CreditCard </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>creditCard</cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> CreditCard-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CreditCardEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>customer</cmr-field-name> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships>
El hecho de que ambos participantes en la relación definan elementos <cmr-field> (campos de relación) nos indica que la relación es bidireccional.
Los beans de entidad también pueden mantener relaciones de cardinalidad mayor de uno. Esto significa que un bean de entidad puede contener muchos otros beans de entidad. Por ejemplo, el EJB Customer puede estar relacionado con muchos EJBs Phone, cada uno de los cuales representa un número de teléfono. Esto es bastante distinto de las relaciones sencillas uno-a-uno. Las relaciones uno-a-muchos y muchos-a-muchos requieren que el desarrollador trabaje con una colección de referencias cuando accede al campo de relación.
Para ilustrar la relación una-a-muchos unidireccional, usaremos un nuevo bean de entidad, el EJB Phone, para el que deberemos definir una tabla, la tabla PHONE:
CREATE TABLE PHONE ( ID INT PRIMARY KEY NOT NULL, NUMBER CHAR(20), TYPE INT, CUSTOMER_ID INT }
Las relaciones uno-a-muchos entre las tablas CUSTOMER y PHONE podrían realizarse de distintas formas en una base de datos relacional. Para este ejemplo hemos decidido incluir en la tabla PHONE una clave foránea a la tabla CUSTOMER.
La tabla con los datos añadidos puede mantener una columna de claves foráneas no únicas. En el caso de los EJBs Customer y Phone, la tabla PHONE mantiene una clave foránea a la tabla CUSTOMER, y uno o más registros PHONE pueden contener claves foráneas hacia el mismo registro CUSTOMER. En otras palabras, en la base de datos los registros PHONE apunto a los registros CUSTOMER. En el modelo abstracto de programación, sin embargo, es el EJB Customer el que apunta a los EJBs Phone. ¿Cómo funciona esto? El sistema contenedor esconde el puntero reverso de forma que parece que es el Customer el que conoce los EJB Phone asociados y no al revés. Cuando le pedimos al contenedor que devuelva una Collection de EJBs Phone (mediante la invocación del método getPhoneNumbers()), preguntará a la tabla PHONE por todos los registros con una clave foránea que se corresponda con la clave primaria del EJB Customer.
Una implementación más sencilla de la relación Customer-Phone podría usar una tabla de enlace que mantenga dos columnas con claves foráneas apuntando tanto a los registros CUSTOMER y PHONE. Podríamos entonces colocar una restricción de unicidad en la columna de clave foránea PHONE para asegurarnos de que sólo contiene entradas únicas (esto es, de que cada teléfono sólo pertenece a un cliente), mientras que podríamos permitir que la columna de clave foránea CUSTOMER tuviera duplicados. La ventaja de la tabla de enlace es que no se modifica ninguna de las dos tablas originales CUSTOMER y PHONE.
En el modelo abstracto de programación representamos una relación a muchos definiendo un campo de relación que pueda apuntar a muchos beans de entidad. Para conseguir esto, empleamos los mismos métodos abstractos de acceso que ya hemos usado en las relaciones uno-a-uno, pero esta vez definimos el tipo del campo como java.util.Collection o java.util.Set. La colección mantiene un grúpo homgéneo de referencias a interfaces locales del objeto EJB.
Por ejemplo, un EJB Customer puede estar relacionado con muchos números de teléfono, cada uno de ellos representado por un EJB Phone. En lugar de tener distintos campos de relación para cada uno de ellos, el EJB Customer mantiene todos los EJBs Phone en un campo de relación basado en colleciones, al que se puede acceder a través de los métodos abstractos de acceso:
public abstract class CustomerBean implements javax.ejb.EntityBean { ... // relationship fields public java.util.Collection getPhoneNumbers(); public void setPhoneNumbers(java.util.Collection phones); public AddressLocal getHomeAddress();() public void setHomeAddress(AddressLocal local); ...
El EJB Phone debe definirse como un bean de entidad con una interfaz local. Al ser la relación unidireccional, no es necesario definir los campos de relación en la clase bean:
// the local interface for the Phone EJB public interface PhoneLocal extends javax.ejb.EJBLocalObject { public String getNumber(); public void setNumber(String number); public byte getType(); public void setType(byte type); } // the bean class for the Phone EJB public class PhoneBean implements javax.ejb.EntityBean { public Integer ejbCreate(String number, byte type) { setNumber(number); setType(type); return null; } public void ejbPostCreate(String number,byte type) { } // persistence fields public abstract Integer getId(); public abstract void setId(Integer id); public abstract String getNumber(); public abstract void setNumber(String number); public abstract byte getType(); public abstract void setType(byte type); // standard callback methods ... }
Para ilustrar cómo un bean de entidad usa un campo de relación basado en una colección, definiremos un método en la clase CustomerBean que permita a los clientes remotos añadir nuevos números de teléfono. El método, addPhoneNumber(), usa los argumentos para crear un nuevo EJB Phone y luego añade ese EJB Phone a un campo de relación basado en una colección llamado phoneNumbers:
public abstract class CustomerBean implements javax.ejb.EntityBean { // business methods public void addPhoneNumber(String number, String type) { InitialContext jndiEnc = new InitialContext(); PhoneHomeLocal phoneHome = jndiEnc.lookup("PhoneHomeLocal"); PhoneLocal phone = phoneHome.create(number,type); Collection phoneNumbers = this.getPhoneNumbers(); phoneNumbers.add(phone); } ... // relationship fields public java.util.Collection getPhoneNumbers(); public void setPhoneNumbers(java.util.Collection phones); ...
Hemos creado primero el EJB Phone y luego lo hemos añadido a la relación añadiéndolo a la colección que define esta relación. La colección phoneNumbers la obtenemos a partir del método de acceso getPhoneNumbers(). Al añadir el EJB Phone a la Collection hace que el contenedor establezca la clave foránea en el nuevo registro PHONE de forma que apunte al registro CUSTOMER del EJB Customer. Si hubiéramos usado una tabla de enlace, se habría creado un nuevo registro. A partir de este momento, el nuevo EJB Phone estará disponible en la relación basada en la colección phoneNumbers.
También es posible actualizar o borrar referencias en una relación basada en una colección usando el método de aceso de la relación. Por ejemplo, el siguiente código define dos métodos en la clase CustomerBean que permiten a los clientes borrar o actualizar números de teléfono en el campo de relación del bean phoneNumbers:
public abstract class CustomerBean implements javax.ejb.EntityBean { // business methods public void removePhoneNumber(byte typeToRemove) { Collection phoneNumbers = this.getPhoneNumbers(); Iterator iterator = phoneNumbers.iterator(); while(iterator.hasNext()) { PhoneLocal phone = (PhoneLocal)iterator.next(); if(phone.getType() = typeToRemove) { iterator.remove(phone); break; } } } public void updatePhoneNumber(String number,byte typeToUpdate) { Collection phoneNumbers = this.getPhoneNumbers(); Iterator iterator = phoneNumbers.iterator(); while(iterator.hasNext()) { PhoneLocal phone = (PhoneLocal)iterator.next(); if(phone.getType() = typeToUpdate) { phone.setNumber(number); break; } } } ... // relationship fields public java.util.Collection getPhoneNumbers(); public void setPhoneNumbers(java.util.Collection phones);
En el método de negocio removePhoneNumber(), se encuentra un EJB Phone con el mismo tipo y se borra de la relación basada en una colección. El EJB no se borra de la base de datos, sino que se elimina su asociación con el EJB Customer.
El método updatePhoneNumber() realmente modifica un EJB Phone existente, cambiando su estado en la base de datos. El EJB Phone sigue siendo referenciado por la relación basada en una colección, pero sus datos han cambiado.
Los métodos removePhoneNumber() y updatePhoneNumber() ilustran que una relación basada en una colección puede accederse y actualizarse exactamente igual que cualquier otro objeto Collection. Además, es posible obtener un java.util.Iterator a partir del objeto Collection y realizar iteraciones con él. Sin embargo, es necesaria cierta precaución cuando se usa un iterador sobre una relación basada en una coleción. No se deben añadir o eliminar elementos del objeto Collection mientras que se está usando el iterador. La única excepción a esta regla es que el método Iterator.remove() puede usarse para eliminar una entrada. Aunque los métodos Collection.add() y Collection.remove() pueden usarse en otras circustancias, si se llaman mientras que el iterador está en uso se generará una excepción java.util.IllegalStateException.
Si no se ha añadido ningún bean al campo de relación phoneNumbers, el método getPhoneNumbers() devolverá un objeto Colletion vacíon. Los campos de relación de tipo <multiplicity> nunca devuelve null. El objeto Collection usado con el campo de relación está implementado por el sistema contenedor y está estrechamente acoplado al funcionamiento interno del contenedor. Esto permite al contenedor EJB implementar mejoras en la eficiencia, como la concurrencia optimista, sin exponer estos mecanismos propietarios al desarrollador del bean (un objeto Collection obtenido a partir de una relación basada en una colección que se materializa en una transacción no puede modificarse fuera del alcance de esas transacción). Los objetos Collection definidos en la aplicación pueden usarse con campos de relación gestionados por el contenedor sólo si los elementos son del tipo apropiado. Por ejemplo, es legar crear un nuevo objeto Collection y luego añadir ese objeto Collection al EJB Customer usando el método setPhoneNumbers():
public void addPhoneNumber(String number, String type) { ... PhoneLocal phone = phoneHome.create(number,type); Collection phoneNumbers = java.util.Vector(); phoneNumbers.add(phone); // This is allowed this.setPhoneNumbers(phoneNumbers); } // relationship fields public java.util.Collection getPhoneNumbers(); public void setPhoneNumbers(java.util.Collection phones);
Si el EJB Customer tuviera una colección de EJB Phone previamente asociados, dejarían de estarlo.
El esquema abstracto de persistencia para las relaciones unidireccionales uno-a-muchos tiene unas cuantas diferencias significativas con respecto a los elementos <ejb-relation> vistos hasta el momento:
<relationships> <ejb-relation> <ejb-relation-name>Customer-Phones</ejb-relation-name> <ejb-relationship-role> <ejb-relationship-role-name> Customer-has-many-Phone-numbers </ejb-relationship-role-name> <multiplicity>One</multiplicity> <relationship-role-source> <ejb-name>CustomerEJB</ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name>phoneNumbers</cmr-field-name> <cmr-field-type>java.util.Collection</cmr-field-type> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <ejb-relationship-role-name> Phone-belongs-to-Customer </ejb-relationship-role-name> <multiplicity>Many</multiplicity> <relationship-role-source> <ejb-name>PhoneEJB</ejb-name> </relationship-role-source> </ejb-relationship-role> </ejb-relation> </relationships>
En el elemento <ejb-relation>, la multiplicidad del EJB Customer se declara como One, mientras que la multiplicidad del <ejb-relationship-role> del EJB Phone es Many. Esto establece obviamente la relación como uno-a-muchos. El hecho de que el <ejb-relationship-role> del EJB Phone no especifique un elemento <cmr-field> indica que la relación uno-a-muchos es unidireccional.
El cambio más interesante es el añadido del elemento <cmr-field-type> en la declaración del <cmr-field> del EJB Customer. El <cmr-field-type> debe especificarse para un bean que tiene un campo de relación basado en una coleción (en este caso, el campo phoneNumbers mantendio por el EJB Customer). El campo <cmr-field-type> sólo puede tener dos valores, java.util.Collection o java.util.Set, que son los tipos permitidos en relaciones basadas en colecciones.