resin-ee $ee/ejb-tut/cmp-one2many.xtp Container Persistent Relations

EJB 2.0 containers can manage relationships between entity beans. In this example, each boarding school house has a number of students. Each house and student has its own entity bean and each student belongs to a house.

Resin-CMP generates the classes and SQL to maintain the relationships automatically. If a student changes houses, Resin-CMP updates both the student and house records. If a student is expelled, both the student and house records get update automatically.

Relationships are updated using the local interface. The second draft of the EJB 2.0 specification distinguishes local from remote interfaces. Local interfaces are useful when using EJB as a database interface or for fast calls within the same JVM.

The tutorial focuses on the classes and configuration for a simple bean/bean relationship. Additional details, like changing houses or creating and deleting students are deferred to another tutorial.

The example has two tables: houses and students. Each student belongs to a single house. It's an n-1 relation like a parent-children relationship.

The database stores the student/house relationship using an field in the student record. The student table points to the house with the house's primary key. The generated code will create the query to list all students belonging to a house.

CREATE TABLE students ( name VARCHAR(250) NOT NULL, house VARCHAR(250), PRIMARY KEY(name) ); CREATE TABLE houses ( name VARCHAR(250) NOT NULL PRIMARY KEY(name) ); INSERT INTO houses VALUES('Gryffindor'); INSERT INTO houses VALUES('Slytherin'); INSERT INTO students VALUES('Harry Potter', 'Gryffindor'); INSERT INTO students VALUES('Ron Weasley', 'Gryffindor'); INSERT INTO students VALUES('Hermione Granger', 'Gryffindor'); INSERT INTO students VALUES('Draco Malfoy', 'Slytherin'); INSERT INTO students VALUES('Millicent Bulstrode', 'Slytherin');

The example client loops through the houses and prints each student from the house. The student beans are returned in a collection.

As in the previous tutorial, Resin-EJB stores the home interfaces in a JNDI context. The student home is stored in and the house home is stored at . The is used since these are the local interfaces for the beans.

The list of students is passed using the JDK 1.2 Collection class and taking students out requires an . Since we are using the local interface, the collection is live. The application could use to add a new sutdent.

... Collection houses = houseHome.findAll(); // iterate through the Houses iter = houses.iterator(); while (iter.hasNext()) { House house = (House); out.println("<h4>" + house.getName() + "</h4>"); students = (Collection) house.getStudentList(); // iterate through the students for that House Iterator studentIter = students.iterator(); while (studentIter.hasNext()) { Student student = (Student); out.println("<li>" + student.getName()); } } .. <h4>Gryffindor</h4> <li>Harry Potter <li>Hermione Granger <li>Ron Weasley <h4>Slytherin</h4> <li>Draco Malfoy <li>Millicent Bulstrode

Both the house and students beans have the three EJB classes: local home interface, local interface, and implementation class. Although the client only sees the local home and local interfaces, most of the interesting work happens in the implementation class. With container managed persistence, most of the work is done automatically, so the classes themselves are short.

The home interface for the house bean implements the minimal method, findByPrimaryKey.

package test.entity.students; import java.rmi.*; import javax.ejb.*; public interface HouseHome extends EJBLocalHome { House findByPrimaryKey(String name) throws FinderException; }

The remote interface returns the name of the house and the collection of students. does not return the container-managed student collection directly. The implementation must first store the managed collection into a concrete implementation.

package test.entity.students; import java.rmi.*; import javax.ejb.*; public interface House extends EJBLocalObject { String getName(); Collection getStudentList(); }

The collection is active. Calling the collection and methods will add and remove students from the house. Adding a student to a house will automatically remove it from any other house. Resin-EJB keeps the relationship consistent.

The house's student list gets contructed from the student records. In the SQL schema, the houses table has no reference to students. So needs to query the student records to find the students in a house. In other words, 's is paired with 's .

The deployment descriptor connects the paired fields and . Resin-EJB can't determine the pairings of relation fields by introspection alone. So you'll need to tell Resin-EJB in the elements.

package example.cmp.relations.one2many; abstract public class HouseBean extends com.caucho.ejb.AbstractEntityBean { /** * Returns the primary key. */ abstract public String getName(); /** * Container-managed relationship can't return directly. */ abstract public Collection getStudentList(); }

The student bean adds the accessor to a basic bean. Since getHouse is paired with the house's , setting a house will automatically change the house's student list to match.

package example.cmp.relations.one2many; import java.rmi.*; import javax.ejb.*; /** * Remote interface for the student home. */ public interface StudentHome extends EJBLocalHome { /** * Returns the named student. */ Student findByPrimaryKey(String name) throws FinderException; }

The student interface returns the student's name and the remote interface of the house. As always, beans never refer to the implementation classes, always the local interface. The student interface must return and never .

package example.cmp.relations.one2many; import java.rmi.*; import javax.ejb.*; public interface Student extends EJBLocalObject { String getName(); House getHouse(); }

The implementation is simple since all the real code is created automatically.

package example.cmp.relations.one2many; abstract public class StudentBean extends com.caucho.ejb.AbstractEntityBean { abstract public String getName(); abstract public House getHouse(); abstract public void setHouse(House house); }

With Resin-EJB, all the Java source can be dropped in WEB-INF/classes. Resin will automatically compile any changes and regenerate the persistence classes, stubs and skeletons.

The deployment descriptor has three sections:
  • House definition
  • Student definition
  • The relationship between the house and student beans.

References to the bean in the relationships section use the definition.

<ejb-jar> <enterprise-beans> <entity> <ejb-name></ejb-name> ... </entity> <entity> <ejb-name></ejb-name> ... </entity> </enterprise-beans> <relationships> <ejb-relation> ... </ejb-relation> </relationships> </ejb-jar>

The house definition is just like the simple entity bean in the previous house tutorial. The main addition is the section. The definition needs to define the studentList field and can specify its return type.

... <entity> <ejb-name></ejb-name> <local-home></local-home> <local></local> <ejb-class></ejb-class> <abstract-schema-name></abstract-schema-name> <primkey-field></primkey-field> <prim-key-class></prim-key-class> <persistence-type></persistence-type> <reentrant></reentrant> <cmp-field><field-name></field-name></cmp-field> </entity> ...

The house definition is also like the simple entity bean in the previous house tutorial. The main addition is the section.

... <entity> <ejb-name></ejb-name> <local-home></local-home> <local></local> <ejb-class></ejb-class> <abstract-schema-name></abstract-schema-name> <primkey-field></primkey-field> <prim-key-class></prim-key-class> <persistence-type></persistence-type> <reentrant></reentrant> <cmp-field><field-name></field-name></cmp-field> </entity> ... TagMeaning ejb-jartop-level containing element enterprise-beansbean provider configuration entitydefines an entity bean ejb-nameThe name of the ejb. Used to tie ejbs together and used in contructing the url. local-homeclass name of the local home interface localclass name of the local interface ejb-classthe bean's implementation class prim-key-classclass of the primary key persistence-typecontainer persistence in this example reentrantthe bean can call itself abstract-schema-namemaps to the SQL table name primkey-fieldwhich field is the primary key cmp-fieldall container managed persistence fields should be listed field-namethe name of a cmp-field cmr-fielddefines a relationship field cmr-field-namethe name of the relationship field cmr-field-typethe name of the collection items

The relationship connects the field of the to the field of the . Although the XML looks a bit forbidding, it's almost entirely boilerplate. You can cut and paste a standard example, only filling in a few fields. In this example, there are only four actual values, surrounded by the verbosity of XML.

... <relationships> <ejb-relation> <ejb-relationship-role> <relationship-role-source> <ejb-name></ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name></cmr-field-name> </cmr-field> </ejb-relationship-role> <ejb-relationship-role> <relationship-role-source> <ejb-name></ejb-name> </relationship-role-source> <cmr-field> <cmr-field-name></cmr-field-name> </cmr-field> </ejb-relationship-role> </ejb-relation> </relationships> ... TagMeaning relationshipscontains all bean/bean relationships ejb-relationa single relation ejb-relationship-roleone end of the relation relationship-role-sourcespecifying the bean ejb-namethe ejb-name of the bean cmr-fieldspecifying the bean's relation cmr-field-namethe relation's name

The student houses example just touches the simplest example of using container managed relations. Even in this basic case, it's easy to see how the EJB 2.0 persistent model takes the drudgery out of accessing databases.

But more important than saving a bit of work, the EJB 2.0 persistence model makes code more flexible and less brittle. Projects can explore data models and refactor code without worrying that the refactoring will break years-old SQL.