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

    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
Column --> ID
    Row             1

Users_CERTIFICATIONS

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 constructor which takes these 3 arguements

         // filename is the the file to be validated and here is a sample         // list - defines all the fields in the above csv file ( a field has index, type, isOptional, rege…

ArrayList vs LinkedList vs HashSet Performance Comparision

ConclusionsInserting & Reading sequentially from Collection prefer LinkedList/ArrayListInserting & Reading/Deleting by Search/equals from Collection prefer HashSetInserting, ArrayList & LinkedList performs best while HashSet takes double the timeReading, HashSet performs best while ArrayList & LinkedList are marginally lessDeleting, HashSet performs 10 times better than ArrayList & ArrayList performs 4 times better than LinkedList. LinkedList is slow because of sequencial search Bottom line : unless you are not going to iterate using for(Integer i : list ) then prefer HashSet
Inserting/Reading/Deleting integer's from zero till countJDK7Collectionactioncounttime msArrayListInsert1000/1LinkedListInsert1000/1HashSetInsert1000/1ArrayListInsert100005LinkedListInsert100004HashSetInsert100007ArrayListInsert10000011LinkedListInsert10000011HashSetInsert10000021ArrayListGet/Read1000LinkedListGet/Read1000HashSetGet/Read1000ArrayListGet/Read100004LinkedListGet/Read100003Has…