Wednesday 19 May 2010

Composite Primary Keys for JPA Entities

When the primary key of a JPA entity is composed from more than one attribute it is necessary to define a class corresponding to the primary key.

The JPA entity will use the @IdClass annotation to specify the name of the class corresponding to the composite primary key.

In the class corresponding to the JPA entity, the attributes included in the primary key will be marked with the @Id annotation.

Example using a compound primary key in JPA

1.Create and populate a table to be used by the example.

ij> CREATE TABLE Grades(
> studentID INTEGER NOT NULL,
> cursID INTEGER NOT NULL,
> semestrul1 INTEGER,
> semestrul2 INTEGER,
> laborator INTEGER,
> CONSTRAINT pk_SidCid PRIMARY KEY(studentID,cursID));
0 rows inserted/updated/deleted
...
ij> INSERT INTO Grades VALUES
> (1,1,9,10,10);
1 row inserted/updated/deleted
...
 ij> SELECT * FROM Grades;
STUDENTID  |CURSID     |SEMESTRUL1 |SEMESTRUL2 |LABORATOR
-----------------------------------------------------------
1          |1          |9          |10         |10
2          |1          |8          |7           |8
3          |1          |10         |10        |10
28 rows selected

2.Define the JPA entity

import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Column;
import javax.persistence.NamedQuery;
import java.io.Serializable;

@Entity
@Table(name="GRADES")
@IdClass(GradesPK.class)
@NamedQuery(name="Grades.forOneStudent",
query="select o from Grades o where o.studentId=:param")

public class Grades implements Serializable{

@Id
@Column(name="STUDENTID", nullable=false)
private int studentId;

@Id
@Column(name="CURSID", nullable=false)
private int cursId;

@Column(name="SEMESTRUL1")
private int semestrul1;

...
}

3.Define the class corresponding to the primary key.

Make sure that the names and types of the fields are the same with those in the entity bean!

import java.io.Serializable;

public class GradesPK implements Serializable{

public int studentId;
public int cursId;

public GradesPK(){
//constructor public fara parametri
}

public GradesPK(int studentId, int cursId){
this.studentId=studentId;
this.cursId=cursId;
}

public int hashCode(){
return super.hashCode();
}

public boolean equals(Object other){

if(!(other instanceof GradesPK)){
return false;}

GradesPK celalalt = (GradesPK)other;

return(celalalt.studentId==studentId
       &&
      celalalt.cursId==cursId);
}
}

4.Define a method in a session bean to use for testing a query.

public List getGradesforOneStudent(int idStud){
List note = (List)em.createNamedQuery("Grades.forOneStudent").
            setParameter("param",idStud).getResultList();

return note;
}


5.Write a test client to call the method.
 
import javax.naming.InitialContext;
import java.util.List;
import java.util.ListIterator;
...

public class Client1{
public static void main(String[] args) throws Exception
{
    InitialContext ctx = new InitialContext();
    FatadaCursuri beanInstance = (FatadaCursuri)
                                      ctx.lookup(FatadaCursuri.class.getName());

    List grades = beanInstance.getGradesforOneStudent(2);
    ListIterator listIterator2 = grades.listIterator();
    System.out.println("Notele studentului cu studentId=2");
    while(listIterator2.hasNext()){
    Grades linie = (Grades)listIterator2.next();
    System.out.print("ID Curs:"+linie.citesteCursId());
    System.out.print(" Nota 1:"+linie.citesteSemestrul1());
    System.out.print(" Nota 2:"+linie.citesteSemestrul2());
    System.out.println(" Nota lab.:"+linie.citesteLaborator());   
    }
}
}

2 comments:

Anonymous said...

When defining the JPA entity, now you need to use @EmbeddedId annotation to specify Primary Key
('now' being March 2011)

javaGirl said...

Thank you for your comment! I've done a little research and here's what I found out:
@EmbeddedId exists since Java Persistence 1.0, to denote a composite primary key that is an embeddable class. Composite primary keys can also be denoted using the javax.persistence.IdClass annotation. When using an EJB generator (Netbeans, JDeveloper,...) one should check which annotation is used to express the composite primary key, since it looks like @EmbeddedID annotation does not work
with foreign keys that are made up of only some columns of the composite primary key.