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