Skip to main content

JPA Compound Primary Keys Explained

The whole purpose of this article is to explain JPA Compound Primary Keys in a simple way.

Some Entities needs to have a primary key based on more than one field (eg Legacy DB), so we don't have much choice here. JPA supports two ways of configuring compound primary keys however we are going to focus on the one which I feel makes Entities and Queries easy to read and understand.

Lets take an example of a PhoneDirectory Entity which stores Phone No, Ext and Employee name, many employees can have same phone no but with different ext so our candidate for primary key is Phone No and Ext. We need to follow just couple of steps write this entity

Step 1:

1. Create PhoneId class as show below
2. This class should have equals(), hashcode() and should implement Serializable
3. And should be annotated with @Embeddable



import java.io.Serializable;
import javax.persistence.Embeddable;

/**
*
* @author intesar
*/
@Embeddable
public class PhoneId implements Serializable {

private String phoneNo;
private String ext;

public PhoneId() {
}

public PhoneId(String phoneNo, String ext) {
this.phoneNo = phoneNo;
this.ext = ext;
}

public String getExt() {
return ext;
}

public void setExt(String ext) {
this.ext = ext;
}

public String getPhoneNo() {
return phoneNo;
}

public void setPhoneNo(String phoneNo) {
this.phoneNo = phoneNo;
}

@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PhoneId other = (PhoneId) obj;
if ((this.phoneNo == null) ? (other.phoneNo != null) : !this.phoneNo.equals(other.phoneNo)) {
return false;
}
if ((this.ext == null) ? (other.ext != null) : !this.ext.equals(other.ext)) {
return false;
}
return true;
}

@Override
public int hashCode() {
int hash = 7;
hash = 47 * hash + (this.phoneNo != null ? this.phoneNo.hashCode() : 0);
hash = 47 * hash + (this.ext != null ? this.ext.hashCode() : 0);
return hash;
}
}




Step 2 : Use PhoneId inside PhoneDirectory Entity


import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

/**
*
* @author intesar
*/
@Entity
@NamedQueries({
@NamedQuery(name="PhoneDirectory.findByPhoneNo", query="SELECT p FROM PhoneDirectory p WHERE p.phoneId.phoneNo = ?1")
})
public class PhoneDirectory implements Serializable {

private static final long serialVersionUID = 1L;
@EmbeddedId
private PhoneId phoneId;
@Column(name = "employee_name")
private String employeeName;

public String getEmployeeName() {
return employeeName;
}

public void setEmployeeName(String employeeName) {
this.employeeName = employeeName;
}

public PhoneId getPhoneId() {
return phoneId;
}

public void setPhoneId(PhoneId phoneId) {
this.phoneId = phoneId;
}

@Override
public int hashCode() {
int hash = 0;
hash += (phoneId != null ? phoneId.hashCode() : 0);
return hash;
}

@Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof PhoneDirectory)) {
return false;
}
PhoneDirectory other = (PhoneDirectory) object;
if ((this.phoneId == null && other.phoneId != null) || (this.phoneId != null && !this.phoneId.equals(other.phoneId))) {
return false;
}
return true;
}

@Override
public String toString() {
return "prepclass1.PhoneDirectory[id=" + phoneId + "]";
}
}




And here are some test cases


import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

/**
*
* @author intesar
*/
public class Main1 {

/**
* @param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
Main1 m = new Main1();
PhoneDirectory pd = new PhoneDirectory();
PhoneId phoneId = new PhoneId("800-000-0000", "123");
pd.setPhoneId(phoneId);
pd.setEmployeeName("Intesar");
m.persist(pd);

System.out.println ( m.executeQuery(phoneId));

System.out.println ( m.executeQuery(phoneId.getPhoneNo()).get(0));


}

public void persist(Object object) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PrepClass1PU");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
try {
em.persist(object);
em.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
em.getTransaction().rollback();
} finally {
em.close();
}
}

public PhoneDirectory executeQuery(PhoneId phoneId) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PrepClass1PU");
EntityManager em = emf.createEntityManager();
try {
return em.find(PhoneDirectory.class, phoneId);
} catch (Exception e) {
e.printStackTrace();
} finally {
em.close();
}
return null;
}

public List executeQuery(String phoneNo) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PrepClass1PU");
EntityManager em = emf.createEntityManager();
try {
return em.createNamedQuery("PhoneDirectory.findByPhoneNo").setParameter(1, phoneNo).getResultList();
} catch (Exception e) {
e.printStackTrace();
} finally {
em.close();
}
return null;
}
}


Also now can notice its so simple to write readable queries and also our entity is easy to understand.

Comments

Popular posts from this blog

Access multiple Databases in JPA

According to JPA specification we can define multiple "persistence-unit" elements (i.e. like below) in persistence.xml file and can easily refer them inside Dao layers as this. public class PolarDaoImpl {     @PersistenceContext(unitName="PolarPU")     protected EntityManager entityManager; -- } public class BearDaoImpl {     @PersistenceContext(unitName="BearPU")     protected EntityManager entityManager; -- } Checkout sample persistence.xml <?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">     <!-- Database 1 -->     <persistence-unit name="PolarPU" transaction-type="RESOURCE_LOCAL">         <

JPA 2 new feature @ElementCollection explained

@ElementCollection is new annotation introduced in JPA 2.0, This will help us get rid of One-Many and Many-One shitty syntax. Example 1: Stores list of Strings in an Entity @Entity public class Users implements Serializable {     private static final long serialVersionUID = 1L;     @Id     @GeneratedValue(strategy = GenerationType.AUTO)     private Long id;     @ElementCollection     private List<String> certifications = new ArrayList <String> ();     public Long getId() {         return id;     }     public void setId(Long id) {         this.id = id;     }     public List <String> getCertifications() {         return certifications;     }     public void setCertifications(List <String> certifications) {         this.certifications = certifications;     } .. }         Users u = new Users();         u.getCertifications().add("Sun Certified Java Programmer");         em.persist(u); Generated Tables    Users    Co

Reuse JPA Entities as DTO

Note : Major design advantages of JPA Entities are they can detached and used across tiers and networks and later can by merged. Checkout this new way of querying entities in JPA 2.0 String ql = " SELECT new prepclass2.Employee (e.firstname, e.lastname) FROM Employee e "; List<Employee> dtos = em.createQuery(ql).getResultList(); The above query loads all Employee entities but with subset of data i.e. firstname, lastname. Employee entity looks like this. @Entity @Table(name="emp") public class Employee implements Serializable {     private static final long serialVersionUID = 1L;     @Id     @GeneratedValue(strategy = GenerationType.AUTO)     private Long id;     @Column     private String firstname;     @Column     private String lastname;     @Column     private String username;     @Column     private String street;     @Column     private String city;     @Column     private String state;     @Column     private String zipc