THERE ARE TWO NEW VERSIONS OF THIS POST.
SIMPLES COMPOSITE KEY: http://uaihebert.com/?p=1674&page=8
COMPLEX COMPOSITE KEY: http://uaihebert.com/?p=1674&page=9
Good afternoon.
Let us talk today about Composite Primary Key (Primary-Key) in a class?
What if only the ID it is not enough to define a unique record of your class? How can we make the JPA understand this situation of two fields as primary-key?
To help us to understand about Composite Key I will use a class named Car; this class has a car chassis serial number as primary key. Imagine that a new software requirement just arrive saying that, we need the Car primary key to be composed by the chassis serial number and with the engine serial number.
We will keep our JPA study at the same point that we have stopped in the last post (JPA SequenceGenerator). If you need any help to compile the code of this post, you can check the others posts about JPA that shows how to set up the environment: JPA TableGenerator – Simple Primay Key, Auto Create Schema Script with: Ant, Hibernate 3 e JPA 2, Tutorial Hibernate 3 with JPA 2.
We need to create a class to be our composite primary-key.
Let us see how the CarPK code will be:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | package com; import java.io.Serializable; public class CarPK implements Serializable { private String chassisSerialNumber; private String engineSerialNumber; public CarPK(){ // Your class must have a no-arq constructor } @Override public boolean equals(Object obj) { if (obj instanceof CarPK){ CarPK carPk = (CarPK) obj; if (!carPk.getChassisSerialNumber().equals(chassisSerialNumber)){ return false ; } if (!carPk.getEngineSerialNumber().equals(engineSerialNumber)){ return false ; } return true ; } return false ; } @Override public int hashCode() { return chassisSerialNumber.hashCode() + engineSerialNumber.hashCode(); } public String getChassisSerialNumber() { return chassisSerialNumber; } public void setChassisSerialNumber(String chassisSerialNumber) { this .chassisSerialNumber = chassisSerialNumber; } public String getEngineSerialNumber() { return engineSerialNumber; } public void setEngineSerialNumber(String engineSerialNumber) { this .engineSerialNumber = engineSerialNumber; } } |
There are some rules that your PK class should follow:
- It must have a default constructor without arguments.
- It must implement the java.io.Serializable interface.
- It must override the methods equals and hashCode.
Let us create now the Car class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | package com; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Table; @Entity @Table (name= "CAR" ) @IdClass (value=CarPK. class ) public class Car { @Id private String chassisSerialNumber; @Id private String engineSerialNumber; @Column private String name; // Yes, some people like to give name to theirs cars. public String getChassisSerialNumber() { return chassisSerialNumber; } public void setChassisSerialNumber(String chassisSerialNumber) { this .chassisSerialNumber = chassisSerialNumber; } public String getEngineSerialNumber() { return engineSerialNumber; } public void setEngineSerialNumber(String engineSerialNumber) { this .engineSerialNumber = engineSerialNumber; } public String getName() { return name; } public void setName(String name) { this .name = name; } } |
Notice that in our Car class, we just added the @Id annotation without the need of any other annotation type. PS.: At the “name” attribute you will find the @Column annotation but this annotation is not necessary in our case.
Let us see our Main class code that will insert a record into the database:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | package com; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public class Main { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory( "Hello" ); EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); Car car = new Car(); car.setChassisSerialNumber( "9BW DA05X6 1 T050136" ); car.setEngineSerialNumber( "ABC123" ); car.setName( "Thunder" ); em.persist(car); em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); e.printStackTrace(); } finally { emf.close(); } System.out.println( "It is over" ); } } |
Run the Main class and check the result in the console.
How can we find our recorded entity that uses composite primary-key? Let us edit our Main class and see the result:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | package com; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; public class Main { public static void main(String[] args) { EntityManagerFactory emf = Persistence.createEntityManagerFactory( "Hello" ); EntityManager em = emf.createEntityManager(); try { em.getTransaction().begin(); CarPK carPK = new CarPK(); carPK.setChassisSerialNumber( "9BW DA05X6 1 T050136" ); carPK.setEngineSerialNumber( "ABC123" ); Car car = em.find(Car. class , carPK); System.out.println(car.getName()); em.getTransaction().commit(); } catch (Exception e) { em.getTransaction().rollback(); e.printStackTrace(); } finally { emf.close(); } System.out.println( "It is over" ); } } |
Run the Main class again.
To find an entity that uses its ID as composite primary-key we need to create an instance of the PK class and use it as parameter in the “find” method.
There is another way to map this primary-key attribute. Notice that we have the same ID fields in both classes: “Car and CarPK“. It both has the chassisSerialNumber and the engineSerialNumber fields. With a little lines added/removed from our code we can refactor it to remove this duplicated fields.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | package com; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Embeddable; @Embeddable public class CarPK implements Serializable { @Column private String chassisSerialNumber; @Column private String engineSerialNumber; public CarPK(){ // Your class must have a no-arq constructor } @Override public boolean equals(Object obj) { if (!(obj instanceof CarPK)){ CarPK carPk = (CarPK) obj; if (!carPk.getChassisSerialNumber().equals(chassisSerialNumber)){ return false ; } if (!carPk.getEngineSerialNumber().equals(engineSerialNumber)){ return false ; } return true ; } return false ; } @Override public int hashCode() { return chassisSerialNumber.hashCode() + engineSerialNumber.hashCode(); } public String getChassisSerialNumber() { return chassisSerialNumber; } public void setChassisSerialNumber(String chassisSerialNumber) { this .chassisSerialNumber = chassisSerialNumber; } public String getEngineSerialNumber() { return engineSerialNumber; } public void setEngineSerialNumber(String engineSerialNumber) { this .engineSerialNumber = engineSerialNumber; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com; import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.Table; @Entity @Table (name = "CAR" ) public class Car { @EmbeddedId private CarPK carPK; @Column private String name; // Yes, some people like to give name to theirs cars. public String getName() { return name; } public void setName(String name) { this .name = name; } public CarPK getCarPK() { return carPK; } public void setCarPK(CarPK carPK) { this .carPK = carPK; } } |
We can see that just a few changes were done in the classes:
- In the CarPK class there is a new annotation: the @Column. The car ID attributes will be mapped only inside the CarPK. There is a new annotation named @Embeddeable allowing our class to be part of another class.
- We removed the Id attributes from the Car class. We also removed the @IdClass annotation. We added an attribute of the CarPK with the @EmbeddedId, this annotation allow the Car to use the CarPK fields as its primary-key.
If you wish, run the main class again and you will see that our class is returned by the find method without errors.
Warning: When your class implements the composite primary-key it will not be able to use the sequence id generation. You will have to generate the ID by your software code.
Do you have any doubts or suggestions? Just post it.
See you later.