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

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;     }     pub...

Validating CSV Files

What is CsvValidator ?   A Java framework which validates any CSV files something similar to XML validation using XSD. Why should I use this ?   You don't have to use this and in fact its easy to write something your own and also checkout its source code for reference. Why did I write this ?   Some of our projects integrate with third party application which exchanges information in CSV files so I thought of writing a generic validator which can be hooked in multiple projects or can be used by QA for integration testing. What is the license clause ?   GNU GPL v2 Are there any JUnit test cases for me checkout ?  Yes,  source How to integrate in my existing project ? Just add the Jar which can be downloaded from here  CsvValidator.jar  and you are good. Instantiate  CsvValidator c onstructor which takes these 3 arguements          // filename is the the file to be validated and here ...

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