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.
one2many.sql
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
java:comp/env/cmp/students
and the house home is stored at java:comp/env/cmp/houses.
The ejb-local 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 Iterator.
Since we are using the local interface, the collection is live.
The application could use students.add(student) to add
a new sutdent.
Servlet Fragment
...
Collection houses = houseHome.findAll();
// iterate through the Houses
iter = houses.iterator();
while (iter.hasNext()) {
House house = (House) iter.next();
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) studentIter.next();
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.
HouseHome.java
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. getStudents does not
return the container-managed student collection directly. The
implementation must first store the managed collection into a
concrete implementation.
House.java
package test.entity.students;
import java.rmi.*;
import javax.ejb.*;
public interface House extends EJBLocalObject {
String getName();
Collection getStudentList();
}
|
The getStudentList collection is active. Calling the collection
add and remove 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 getStudentList needs to query the student records to
find the students in a house. In other words, House's getStudentList is
paired with Student's getHouse.
The deployment descriptor connects the paired fields getStudentList
and getHouse. Resin-EJB can't determine the pairings of
relation fields
by introspection alone. So you'll need to tell Resin-EJB in the
ejb-relationship elements.
HouseBean.java
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 getHouse accessor to a basic bean.
Since getHouse is paired with the house's getStudentList,
setting a house will automatically change the house's student list to match.
StudentHome.java
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
House and never HouseBean.
Student.java
package example.cmp.relations.one2many;
import java.rmi.*;
import javax.ejb.*;
public interface Student extends EJBLocalObject {
String getName();
House getHouse();
}
|
The StudentBean implementation is simple since all the real code is
created automatically.
StudentBean.java
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 ejb-name definition.
WEB-INF/one2many.ejb -- overview
<ejb-jar>
<enterprise-beans>
<entity>
<ejb-name>houses</ejb-name>
...
</entity>
<entity>
<ejb-name>students</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 cmr-field
section. The houses definition needs to define the
studentList field and can specify its return type.
WEB-INF/one2many.ejb -- houses
...
<entity>
<ejb-name>houses</ejb-name>
<local-home>example.cmp.relations.one2many.HouseHome</local-home>
<local>example.cmp.relations.one2many.House</local>
<ejb-class>example.cmp.relations.one2many.HouseBean</ejb-class>
<abstract-schema-name>houses</abstract-schema-name>
<primkey-field>name</primkey-field>
<prim-key-class>String</prim-key-class>
<persistence-type>Container</persistence-type>
<reentrant>True</reentrant>
<cmp-field><field-name>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 cmr-field
section.
WEB-INF/student.ejb -- students
...
<entity>
<ejb-name>students</ejb-name>
<local-home>example.cmp.relations.one2many.StudentHome</local-home>
<local>example.cmp.relations.one2many.Student</local>
<ejb-class>example.cmp.relations.one2many.StudentBean</ejb-class>
<abstract-schema-name>student</abstract-schema-name>
<primkey-field>name</primkey-field>
<prim-key-class>String</prim-key-class>
<persistence-type>Container</persistence-type>
<reentrant>True</reentrant>
<cmp-field><field-name>name</field-name></cmp-field>
</entity>
...
|
Tag | Meaning
|
ejb-jar | top-level containing element
|
enterprise-beans | bean provider configuration
|
entity | defines an entity bean
|
ejb-name | The name of the ejb. Used to tie ejbs together and
used in contructing the url.
|
local-home | class name of the local home interface
|
local | class name of the local interface
|
ejb-class | the bean's implementation class
|
prim-key-class | class of the primary key
|
persistence-type | container persistence in this example
|
reentrant | the bean can call itself
|
abstract-schema-name | maps to the SQL table name
|
primkey-field | which field is the primary key
|
cmp-field | all container managed persistence fields should be listed
|
field-name | the name of a cmp-field
|
cmr-field | defines a relationship field
|
cmr-field-name | the name of the relationship field
|
cmr-field-type | the name of the collection items
|
The relationship connects the studentList field of the House
to the house field of the Student. 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.
WEB-INF/student.ejb -- relationships
...
<relationships>
<ejb-relation>
<ejb-relationship-role>
<relationship-role-source>
<ejb-name>students</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>house</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
<ejb-relationship-role>
<relationship-role-source>
<ejb-name>houses</ejb-name>
</relationship-role-source>
<cmr-field>
<cmr-field-name>studentList</cmr-field-name>
</cmr-field>
</ejb-relationship-role>
</ejb-relation>
</relationships>
...
|
Tag | Meaning
|
relationships | contains all bean/bean relationships
|
ejb-relation | a single relation
|
ejb-relationship-role | one end of the relation
|
relationship-role-source | specifying the bean
|
ejb-name | the ejb-name of the bean
|
cmr-field | specifying the bean's relation
|
cmr-field-name | the 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.
Copyright © 1998-2005 Caucho Technology, Inc. All rights reserved.
Resin® is a registered trademark,
and HardCoretm and Quercustm are trademarks of Caucho Technology, Inc. | |
|