Oct 4, 2011

JPA composite primary key example

JPA composite primary key requires careful implementation of Entity’s ID class,

There are two ways of it mentioned in JPA documentations

1)      Using Embeddable Composite Primary Key Class (uses @ Embeddable annotation)
2)      Using Non-Embedded Composite Primary Key Class (uses IDClass annotation from JPA)

Both above mentioned approaches are easy to implement, I anyhow selected second approach to work with in my project.

Following are the steps to implement


1) Created an Entity Class say Customer, which I annotated with @Entity annotation from JPA.

My Customer class looks like as given below:






@Entity
@Table(name="customer")
public class Customer implements Serializable {
     private static final long serialVersionUID = 1L;
    
     @Id
     @Column(name ="name")
     private Integer customerName;
     @Id
     @Column(name = "customer_id")
     private Integer customerId;
   
     /**
      * @return the customerId
      */
     public Integer getCustomerId() {
           return customerId;
     }
     /**
      * @param customerId the customerId to set
      */

     public void setCustomerId(Integer customerId) {
           this.customerId = customerId;
     }

     /**
      * @return the customerName
      */

     public Integer getCustomerName() {
          return customerName;
     }
     /**
      * @param customerName the customerName to set
      */
     public void setCustomerName(Integer customerName) {
           this.customerName = customerName;
     }
}




I have annotated both customerName and customerId with @Id, and Customer class with @Entity annotation.

3)   As we have a composite primary key for Customer Entity, so it requires for us to have an IDClass which mentions both the keys as parameters.
4)      I constructed the required ID class as given below




public class CustomerCompositeKey {
     private Integer customerName;
     private Integer customerId;
}




While testing above composite key, we will surely get some exception it doest not look complete and compliance with JPA documentation,  anyhow I run the test to retrieve data from database and got the following exception

Exception in thread "main" java.lang.IllegalArgumentException: to.CustomerCompositeKey cannot be cast to java.io.Serializable
       at org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:191)
       at mgr.CustomerMgr.getCustomer(CustomerMgr.java:24)
   at Test.main(Test.java:51)


Above exception caused by the not having Serialization interface implementation with my Composite key

So I changed my class further

public class CustomerCompositeKey implements Serializable{
    
     private static final long serialVersionUID = 1L;

If you follow the JPA document, they mention following rules for Composite key class link

·  Must be a POJO class, with public access and with a public constructor.
· properties of composite primary key class must be public or protected.
·  Serializable; and having implementation of  equals and hashCode methods.

Upon applying following changes my composite key evolved further as given below

/**

 *
@author varun

 *

 */

public class CustomerCompositeKey implements Serializable{
     private static final long serialVersionUID = 1L;
     public  String customerName;
     public  Integer customerId;
     public CustomerCompositeKey(){
     }
     public CustomerCompositeKey(String name, Integer id){
           this.setCustomerId(id);
           this.setCustomerName(name);
     }
     /**
      * @return the customerName
      */
     public String getCustomerName() {
           return customerName;
     }
     /**
      * @param customerName the customerName to set
      */
     public void setCustomerName(String customerName) {
           this.customerName = customerName;
     }
     /**
      * @return the customerId
      */
     public Integer getCustomerId() {
           return customerId;
     }
     /**
      * @param customerId the customerId to set
      */
     public void setCustomerId(Integer customerId) {
          this.customerId = customerId;
     }
     public int hashCode() {
        return (int) customerId.hashCode();
    }
   public boolean equals(Object obj) {
        if (obj == this) return true;
        if (!(obj instanceof ConfigNodeTypeId)) return false;
        if (obj == null) return false;
        CustomerCompositeKey pk = (CustomerCompositeKey) obj;
        return pk.customerId.equals(customerId);
    }
}



Upon testing, it throws another exception, which says

Exception in thread "main" java.lang.IllegalArgumentException: Provided id of the wrong type. Expected: class java.lang.String, got class to.CustomerCompositeKey
       at org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:188)
       at mgr.CustomerMgr.getCustomer(CustomerMgr.java:24)
   at Test.main(Test.java:51)

This is actually taking the default type of parameter for Customer entity instead of a composite key to match and find the record, this is because we have forgotten the very important step of including @IDClass annotation to bind the Entity to ID Class or composite key.

We further modify our Customer Entity class as

@Entity
@IdClass(CustomerCompositeKey.class)
@Table(name="customer")
public class Customer implements Serializable {
..
..

Upon testing further following Exception printed


 [ WARN][org.hibernate.util.JDBCExceptionReporter:71]SQL Error: 1054, SQLState: 42S22
[ERROR][org.hibernate.util.JDBCExceptionReporter:72]Unknown column 'customer0_.customerName' in 'field list'
Exception in thread "main" javax.persistence.PersistenceException: org.hibernate.exception.SQLGrammarException: could not load an entity: [to.Customer#component[customerName,customerId]{customerName=null, customerId=null}]
       at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:647)
       at org.hibernate.ejb.AbstractEntityManagerImpl.find(AbstractEntityManagerImpl.java:194)
       at mgr.CustomerMgr.getCustomer(CustomerMgr.java:24)
   at Test.main(Test.java:51)


This is found to be strange, even after following all the steps mentioned by JPA documentation, still there is trap.

Actually it says the particular column are not in the table, or your key’s entity names are not matching with your column, any way change the names to  matching your table column than you also have to change the names in the Entity class. But simpler solution is to annotate your IDClass or composite key attributes with @Column and in the name, write your database column name which are also mentioned in the Entity class.


Here is the updated code for CustomerCompositKey.class






public class CustomerCompositeKey implements Serializable{
     private static final long serialVersionUID = 1L;
     @Column(name ="name")
     private String customerName;
     @Column(name = "customer_id")
     private Integer customerId;
     .. .. ..
     .. .. ..


This would make it finally run and achieve the desired results.

Hope above explanation clears all doubts of composite key in JPA, queries are welcomed.

Thanks

1 comment:

  1. 0 down vote favorite


    User Table

    UserName pk fname lname

    GroupMaster

    GroupName pk UserName pk, fk

    I want to insert data in GroupMaster but my problem is it has composite primary key

    How can I insert data in table through entity manager object?

    how can i persist data in both table
    i want to add UserRole and User nam in GoroupMaster

    em.persist(?)

    ReplyDelete