XDoclet: Entity Bean Relationships
Auto-loading of children is one of the great timesavers in an EJB application. Knowing that when you load an entity all the children associated with it are also already populated makes developing an EJB application a lot more pleasurable than if it were missing this feature. Having said that, setting up relationships so that they load properly can be extremely difficult, especially given the diverse landscape of application servers that exist today. Fortunately, XDoclet makes this task somewhat less painful. However, even with XDoclet it is not a trivial matter.
I have found that if I approach a new EJB application with a to-do list, the initial creation of the EJBs, although tedious, becomes less painful. Generally, when the database is already designed and/or exists, I approach the application in the following order:
- Identify and code the entity objects.
- Test the entity objects to ensure that they load properly.
- Test the entity objects to ensure that they save properly.
- Identify and code the relationships between the objects.
- Test the entity relationships to ensure that they load properly.
- Test the entity relationships to ensure that they save properly.
It is quite common to see these six steps rolled up into one step, and that leads to difficulty and confusion. Invariably it becomes a chicken-and-egg situation. An entity object cannot be tested because it has dependencies on other entities that are not created yet and so forth. To avoid this situation, it is best to create each entity as its own separate object first and then tackle the job of associating the entities to each other.
Once the entities are defined, the job of associating them together becomes that much simpler. Detailed below are the most common types of relationships used in a normal enterprise application. For each of these types of relationships I have detailed how to define the relationship using XDoclet.
For each of these examples, I use two example entities. Although the relationship methods vary depending on the relationship involved, the rest of the objects remain the same. To make things clear most of the time, I will be using the following two objects.
First, the parent object used for these examples:
package com.dzrealms.example.entity; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.RemoveException; import javax.ejb.CreateException; import javax.ejb.FinderException; import java.util.Set; import java.util.Collection; /** * @ejb.bean name="ExampleBean" * jndi-name="example/remote/ExampleBean" * local-jndi-name="example/local/ExampleBean" * cmp-version="2.x" * primkey-field="ID" * schema="Example" * * @ejb.persistence table-name="example" * * @ejb.interface remote-class="com.dzrealms.example.entity.ExampleRemote" * local-class="com.dzrealms.example.entity.ExampleLocal" * * @ejb.home remote-class="com.dzrealms.example.entity.ExampleHomeRemote" * local-class="com.dzrealms.example.entity.ExampleHomeLocal" * * @jboss.entity-command name="mysql-get-generated-keys" * * @jboss.unknown-pk class="java.lang.Integer" * auto-increment="true" */ public abstract class ExampleBean implements EntityBean { /** * @ejb.create-method * @return Primary Key * @throws CreateException */ public Integer ejbCreate() throws CreateException { return null; } public void ejbPostCreate() { } /** * @ejb.select query="select o.ID from Example o where o.name = ?1" * @param s Name we are searching with * @return Set of Primary Keys with that name * @throws FinderException */ public abstract Set ejbSelectByName(String s) throws FinderException; /** * @ejb.interface-method * @ejb.persistence * @return Primary Key */ public abstract Integer getID(); /** * @param i Set the primary key */ public abstract void setID(Integer i); /** * @ejb.interface-method * @ejb.persistence * @return the name of this bean */ public abstract String getName(); /** * @param s Set the name of this bean */ public abstract void setName(String s); /** * @ejb.interface-method * @ejb.persistence * @return the value of this bean */ public abstract String getValue(); /** * @ejb.interface-method * @param s Set the value of this bean */ public abstract void setValue(String s); public void setEntityContext(EntityContext context) {} public void unsetEntityContext() {} public void ejbRemove() throws RemoveException {} public void ejbActivate() {} public void ejbPassivate() {} public void ejbLoad() {} public void ejbStore() {} }
Second, the child object used for these examples:
package com.dzrealms.example.entity; import javax.ejb.EntityBean; import javax.ejb.EntityContext; import javax.ejb.RemoveException; import javax.ejb.CreateException; /** * @ejb.bean name="ExampleChildBean" * jndi-name="example/remote/ExampleChildBean" * local-jndi-name="example/local/ExampleChildBean" * cmp-version="2.x" * primkey-field="ID" * schema="ExampleChild" * * @ejb.persistence table-name="exampleChild" * * @ejb.interface * remote-class="com.dzrealms.example.entity.ExampleChildRemote" * local-class="com.dzrealms.example.entity.ExampleChildLocal" * * @ejb.home * remote-class="com.dzrealms.example.entity.ExampleChildHomeRemote" * local-class="com.dzrealms.example.entity.ExampleChildHomeLocal" * * @jboss.entity-command name="mysql-get-generated-keys" * * @jboss.unknown-pk class="java.lang.Integer" * auto-increment="true" */ public abstract class ExampleChildBean implements EntityBean { /** * @ejb.create-method * @return Primary Key * @throws javax.ejb.CreateException */ public Integer ejbCreate() throws CreateException { return null; } public void ejbPostCreate() {} /** * @ejb.interface-method * @ejb.persistence * @return Primary Key */ public abstract Integer getID(); /** * @param i Set the primary key */ public abstract void setID(Integer i); /** * @ejb.interface-method * @ejb.persistence * @return the test variable */ public abstract String getTestVariable(); /** * @ejb.interface-method * @param s sets the test variable */ public abstract void setTestVariable(String s); public void setEntityContext(EntityContext context) {} public void unsetEntityContext() {} public void ejbRemove() throws RemoveException {} public void ejbActivate() {} public void ejbPassivate() {} public void ejbLoad() {} public void ejbStore() {} }
One to Many
Arguably the most common relationship used, the one-to-many relationship, is also fairly easy to define in XDoclet. In our example, the parent object (ExampleBean) will contain a collection of children (ExampleChildBean). Therefore, the following methods need to be added to the parent with the corresponding XDoclet tags:
/** * @ejb.interface-method * * @ejb.relation name="Parent-to-Children" * role-name="Parent-has-many-Children" * target-ejb="ExampleChildBean" * target-role-name="Child-has-one-parent" * * @jboss.relation fk-column="parentID" * related-pk-field="ID" * * @return */ public abstract Collection getChildren(); /** * @ejb.interface-method */ public abstract void setChildren(Collection c);
Here is a breakdown of the XDoclet tags that explain this relationship:
- @ejb.interface-method
This is a standard XDoclet tag that identifies this method as one that is to be included in the local and/or remote interface. Without this tag, XDoclet would not add this method to the interfaces and therefore it could be used only by the bean itself.
- @ejb.relation name
This tag sets the name of this relationship. This tag is used both to describe the relationship and to provide a unique reference so that the two sides of the relationship can be paired up during the code-generation phase.
- @ejb.relation role-name
This tag defines the name of this side of the relationship. This tag is used to describe this bean's role in the relationship and is used as a reference for XDoclet to determine which methods are associated with what relationship. See @ejb. relation target-role-name below.
- @ejb.relation target-ejb
Defines the name of the bean on the other side of the relationship. This tag tells XDoclet where to look for the corresponding relationship methods.
- @ejb.relation target-role-name
This is the other bean's role-name. XDoclet uses this tag to match up the two sides of the relationship.
- @jboss.relation fk-column
This is the foreign key column name. Note that this is the name of the column itself, not the name of a getter method inside the bean. Because a getter is not required in a relationship, XDoclet refers to the column in the database itself.
- @jboss.relation related-pk-field
This is the key column on the other bean that links up with the foreign key column in this bean's table. Note again that it references the column itself, not a getter method inside of the bean.
The child side of this relationship looks quite similar to the parent side. The following methods need to be added to the ExampleChildBean class:
/** * @ejb.interface-method * * @ejb.relation name="Parent-to-Children" * role-name="Child-has-one-parent" * target-ejb="ExampleBean" * target-role-name="Parent-has-many-Children" * target-multiple="true" * * @jboss.relation fk-column="parentID" * related-pk-field="ID" * * @return */ public abstract ExampleLocal getParent(); /** * @ejb.interface-method */ public abstract void setParent(ExampleLocal el);
As you can see, most of the tags are simply the mirror of the tags defined in the parent object. The role-name is naturally changed, as is the target-role-name and the target-ejb tags. The only new tag in the child is @ejb.relation target-multiple. This is a simple true/false tag that tells XDoclet whether or not this is the many side of a one-to-many relationship.