<document>
  <header>
    <product>resin-ee</product>
    <resin-2.0>$ee/ejb-tut/cmp-map.xtp</resin-2.0>
    <title>Map Relations and Compound Primary Keys</title>
    <description>
      <p>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.</p>
    </description>
  </header>

  <body>
    <summary/>

<p>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.</p>

<p>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.</p>

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

<p>Part of the the primary key class looks like:</p>

<example>
public class GradeKey {
  public Student student;
  public Course course;
  ...
</example>

<p>The read-only <code/getGrades/> map relation belongs to the <code/Student/>
bean.  The map's key is the <code/Course/> object (not the key for
the <code/Course/>.)  Resin-CMP will use the <code/Student/> object
as the <var/student/> field of the composite key, and it will
use a <code/Course/> argument to the <code/Map.get(course)/> call as
the other key.  Since a <code/Student/> and a <code/Course/>
uniquely identify a <code/Grade/>, Resin-CMP can use the combined
key to look up the <code/Grade/> object.</p>

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

<p>The map relation is read-only since its data comes from the <code/Grade/>
table.  In other words, it makes no sense to call <code/getGrades().put(...)/>.
Since <code/Grades/> beans are created with <code/ejbCreate/> and destroyed
with <code/ejbRemove/>, it doesn't make sense for the <code/Map/> to be writable.</p>

<p>The <code/Map/> is a live object.  In other words, when a new <code/Grade/> is
created or an old one is destroyed, the <code/Map/> object is automatically
updated.  Of course, the <code/Map/> needs to be used in the same transaction
as the <code/getGrades()/> call.</p>

<section title="Database Schema">

<p>The database has tables for <code/Students/>, <code/Courses/>,
and <code/Grades/>.  The <code/Grade/> table is dependent on
both the <code/Students/> and the <code/Courses/>.
Because of this dependency, Resin-CMP will automatically delete a student's
grades when the student is removed.</p>

<example title="select.sql">
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)
);
</example>

<p>The <code/Student/>'s map relation is implemented with one main SELECT query
for the <code/keySet()/>.  The <code/get()/> method is implemented by calling 
<code/GuestHome/>'s <code/findByPrimaryKey/> method.  The <code/keySet()/>
returns a set of <code/Course/> beans and the query looks like:</p>

<p>The <code/get(Object)/> method takes a <code/Course/> argument
and returns a <code/Grade/> object.  The query looks like:</p>

<example title='getGrades.get(Course) query'>
SELECT g.course FROM map_grades AS g WHERE g.student=?
</example>

</section>

<section title="Client Servlet using Map">

<p>The example client servlet uses the <code/Map/> like any other
<code/java.util.Map/>.  The servlet iterates over all the students
and displays their grades.  The <code/Courses/> are found using
the <code/getGrades().keySet()/> iterator and then the <code/Grade/>
itself is found using the <code/getGrades().get(Object)/> map.</p>

<example>
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("&lt;title>Student Grades&lt;/title>");
  out.println("&lt;h1>Student Grades&lt;/h1>");

  Iterator iter = students.iterator();
  while (iter.hasNext()) {
    Student student = (Student) iter.next();

    out.println("&lt;h3>" + student.getName() + "&lt;/h3>");

    Map grades = student.getGrades();
      
    out.println("&lt;table>");
    out.println("&lt;tr>&lt;th>Course&lt;th>Grade");

    Iterator courseIter = grades.keySet().iterator();
    while (courseIter.hasNext()) {
      Course course = (Course) courseIter.next();

      Grade grade = (Grade) grades.get(course);

      out.print("&lt;tr>&lt;td>" + course.getName() + "&lt;td>" + grade.getGrade());
    }
    out.println("&lt;/table>");
  }
}
</example>
<results>
&lt;h3>Harry Potter&lt;/h3>
&lt;tr>&lt;td>Course         &lt;td>Grade
&lt;tr>&lt;td>Potions        &lt;td>C-
&lt;tr>&lt;td>Transfiguration&lt;td>B+
&lt;h3>Hermione Granger&lt;/h3>
&lt;tr>&lt;td>Course         &lt;td>Grade
&lt;tr>&lt;td>Potions        &lt;td>A
&lt;tr>&lt;td>Transfiguration&lt;td>A+
&lt;h3>Ron Weasley&lt;/h3>
&lt;tr>&lt;td>Course         &lt;td>Grade
&lt;tr>&lt;td>Potions        &lt;td>C+
&lt;tr>&lt;td>Transfiguration&lt;td>B
</results>

</section>

<section title='Compound Primary Keys'>

<p>A compound primary key is made up of a combination of
persistent fields or identifying relations.  This example uses two
identifying relations: <code/Student/> and <code/Course/>.
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.</p>

<p>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 <code/java.util.Map/> collections in the parent map, like the
<code/getGrades()/> method in the <code/Student/> bean.
The identifying key corresponding to the bean, e.g. <code/Student/>,
is used to select the grades.  The second key, in this case
<code/Course/>, is used as the <code/keySet()/>.  The bean itself is
the value.</p>

<example title='Grade.java'>
package example.cmp.map;

public interface Grade extends javax.ejb.EJBLocalObject {
  public Student getStudent();
  public Course getCourse();

  public String getGrade();
}
</example>

<p>The bean key is a public class with public fields for each
key component.  In this case, the <code/GradeKey/> class has
both a public field for the student and the <code/Course/>.</p>

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

<example title='GradeKey.java'>
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();
  }
}
</example>

</section>

<section title='Conclusion'>

<p>Many database entities need to store named attributes, e.g. a
<code/Student/>
needs to store her <code/Grades/>, indexed by the <code/Course/> she took.  By
implementing this common pattern directly with the standard <code/Map/> 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.</p>

</section>
  </body>
</document>
