resin-ee $ee/ejb-tut/cmp-map.xtp Map Relations and Compound Primary Keys

This tutorial introduces compound primary keys and map relations. Both were initially supported in Resin-CMP 1.0.4. Applications can take advantage of these more advanced CMP features to more cleanly model database tables.

Compound primary keys are used for beans with two or more fields as the primary key. A compound primary key is implemented by creating a special key class containing the fields.

Map relations are allowed for entity beans with a primary key pair where one of the primary keys is an identifying relation. The child bean's identifying relation implicitly selects the parent bean and the other key is used as the Map relation's key field.

An identifying relation with a two-field compound primary key can support a map relation. In this example, each has a for each he takes. The bean has two primary keys: the and the . The two keys are identifying relations since they refer directly to the and objects, not indirectly through their keys.

Part of the the primary key class looks like:

public class GradeKey { public Student student; public Course course; ...

The read-only map relation belongs to the bean. The map's key is the object (not the key for the .) Resin-CMP will use the object as the field of the composite key, and it will use a argument to the call as the other key. Since a and a uniquely identify a , Resin-CMP can use the combined key to look up the object.

public interface Student extends EJBLocalObject { String getName(); java.util.Map getGrades(); }

The map relation is read-only since its data comes from the table. In other words, it makes no sense to call . Since beans are created with and destroyed with , it doesn't make sense for the to be writable.

The is a live object. In other words, when a new is created or an old one is destroyed, the object is automatically updated. Of course, the needs to be used in the same transaction as the call.

The database has tables for , , and . The table is dependent on both the and the . Because of this dependency, Resin-CMP will automatically delete a student's grades when the student is removed.

CREATE TABLE map_students ( name VARCHAR(250) NOT NULL, PRIMARY KEY(name) ); CREATE TABLE map_courses ( name VARCHAR(250) NOT NULL, PRIMARY KEY(name) ); CREATE TABLE map_grades ( student VARCHAR(250) NOT NULL REFERENCES map_students(name), course VARCHAR(250) NOT NULL REFERENCES map_courses(name), grade VARCHAR(10), PRIMARY KEY(student, course) );

The 's map relation is implemented with one main SELECT query for the . The method is implemented by calling 's method. The returns a set of beans and the query looks like:

The method takes a argument and returns a object. The query looks like:

SELECT g.course FROM map_grades AS g WHERE g.student=?

The example client servlet uses the like any other . The servlet iterates over all the students and displays their grades. The are found using the iterator and then the itself is found using the map.

public void service(HttpServletRequest req, HttpServletResponse res) throws java.io.IOException, ServletException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); // for all students, print their grades. Collection students = null; try { students = studentHome.findAll(); } catch(javax.ejb.FinderException e) { throw new ServletException(e); } out.println("<title>Student Grades</title>"); out.println("<h1>Student Grades</h1>"); Iterator iter = students.iterator(); while (iter.hasNext()) { Student student = (Student) iter.next(); out.println("<h3>" + student.getName() + "</h3>"); Map grades = student.getGrades(); out.println("<table>"); out.println("<tr><th>Course<th>Grade"); Iterator courseIter = grades.keySet().iterator(); while (courseIter.hasNext()) { Course course = (Course) courseIter.next(); Grade grade = (Grade) grades.get(course); out.print("<tr><td>" + course.getName() + "<td>" + grade.getGrade()); } out.println("</table>"); } } <h3>Harry Potter</h3> <tr><td>Course <td>Grade <tr><td>Potions <td>C- <tr><td>Transfiguration<td>B+ <h3>Hermione Granger</h3> <tr><td>Course <td>Grade <tr><td>Potions <td>A <tr><td>Transfiguration<td>A+ <h3>Ron Weasley</h3> <tr><td>Course <td>Grade <tr><td>Potions <td>C+ <tr><td>Transfiguration<td>B

A compound primary key is made up of a combination of persistent fields or identifying relations. This example uses two identifying relations: and . Only the home interface is different for a compound primary key bean. The local interface and the bean implementation are just like single key beans.

Compound primary keys may have any number of fields. The special case of two fields with at least on being an identifying relation allows for collections in the parent map, like the method in the bean. The identifying key corresponding to the bean, e.g. , is used to select the grades. The second key, in this case , is used as the . The bean itself is the value.

package example.cmp.map; public interface Grade extends javax.ejb.EJBLocalObject { public Student getStudent(); public Course getCourse(); public String getGrade(); }

The bean key is a public class with public fields for each key component. In this case, the class has both a public field for the student and the .

The compound key class must override the and the methods. If all the component keys are identical, the keys must be identical, even if they are separate Java objects. Resin-CMP needs both and for the bean's caching.

package example.cmp.map; public class GradeKey { public Student student; public Course course; public GradeKey() { } public GradeKey(Student student, Course course) { this.student = student; this.course = course; } public boolean equals(Object obj) { if (! (obj instanceof GradeKey)) return false; GradeKey key = (GradeKey) obj; return student.equals(key.student) && course.equals(key.course); } public int hashCode() { return 65521 * student.hashCode() + course.hashCode(); } }

Many database entities need to store named attributes, e.g. a needs to store her , indexed by the she took. By implementing this common pattern directly with the standard API, and avoiding working directly with JDBC, applications using Resin-CMP can create cleaner Java code, avoid common maintenance issues during development, and take automatic advantage of database caching.