Java Persistence API is quite a specification in that it provides seamless persistence in an easy way, and is also configurable. Writing an object-relational mapping framework is not for the faint-hearted, and takes a lot of time and supreme amount of energy to get the correct operation, let alone standardize how different solutions can work together.
We shall delve deeper into building the metadata for the entity beans in order to get the best out of JPA for our professional project requirements, starting with the mapping of entities to the database table.
We learnt that by simply applying the @Entity
annotation to a POJO, we could turn it into a persistence capable object, an entity bean. We also learnt earlier in this chapter that we control how the fields and the property accessors can be mapped explicitly to a database column.
Through the use of metadata, most of the time using the annotation, we build a picture of the fields for the benefit of the object-relation mapping. The JPA provider takes this information and works on our behalf to transparently manage the persistence entities.
The @javax.persistence.Table
annotation specifies more control in how an entity bean is mapped to the underlying database. It allows developers to define the best database table that an entity is mapped to.
A table of the @Table
parameters is as follows:
Name |
Type |
Description |
Default Value |
---|---|---|---|
Name |
String |
Specifies the name of the target database table. |
Derived by the JPA provider from the entity bean class name |
Catalog |
String |
Specifies the database catalog for the target table. |
The default database catalog |
Schema |
String |
Specifies the database schema for the target table. |
The default database schema for the application |
uniqueConstraints |
|
Specifies additional SQL constraints to be applied on the SQL CREATE of the table. These are only applied if the table generation is enabled. |
No additional constraints |
Indexes |
|
Specifies additional indexes for the table. These are only applied if table generation is enabled. |
No additional indexes |
An example of all of the table specific annotations used to add metadata for a fictional Payroll
entity bean, which is compatible with the JPA 2.1 specification, is as follows:
@Entity @Table(name = "PAYROLL_EZ", schema = "HUMAN_RESOURCES", catalog = "EUROPE", uniqueConstraints = @Unique(columnNames = "PAYROLL_PRINT_SER"), indexes = {@Index(name = "index1", columnNames = {"firstName", "lastName"})) public class Payroll implements java.io.Serializable { /*...*/ @Id long payrollId; String firstName; String lastName; @Column(name = "PAYROLL_PRINT_SER") String payrollSerial; /*...*/ }
The entity Payroll
is mapped to the target database table named PAYROLL_EZ
, the schema HUMAN_RESOURCES
, and the catalog EUROPE
.
We create a unique constraint for this table; we do not want to payroll the print rolls to have the same ID for two or more different employees or state members! We create a specific database index for this payroll database table such that searching for staff with their first and last names will perform faster than normal. (Ideally, in our fictional organization, no two employees will ever have the same first and last name, no matter how it grows in future years.)
All entity beans must have a primary key to ensure that they can be persisted to the database. An entity is a lightweight persistence capable domain object, and the primary key is the reference to locating the row in the database table of the underlying database.
JPA is not designed for entities that do not have any representation of a primary key. For such functional requirements then, you probably have to think about a specific document database solution outside of the Java EE 7 standard.
We have seen how to map an entity to a database with one column representing the primary key.
Recap of the entity named ContactConsumer1
, with a single field named contactId
, representing the single primary key column CONTACT_ID
, in the database table CONTACT_CONSUMER_1
is as follows:
packageje7hb.basic2.jpa; import javax.persistence.*; import java.io.Serializable; @Entity public class ContactConsumer1 implements Serializable { @Id private long contactId; @Column(name = "FIRST_NAME", nullable = false) private String firstname; @Column(name = "LAST_NAME", nullable = false) private String lastname; public long getContactId() {return contactId;} public void setContactId(long contactId) { this.contactId = contactId; } public String getFirstname() {return firstname;} public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() {return lastname;} public void setLastname(String lastname) { this.lastname = lastname; } // toString() method and constructors omitted }
This is the simplest way of mapping an entity with a single primary key.
JPA can also be an entity map with more than one primary key to multiple database columns. There are two ways to create composite primary keys with JPA.
The first way maps a POJO with a separate class that contains a set of primitive fields, and the getter and setter methods. The annotation @javax.persistence.IdClass
specifies a composite primary key class on the target entity.
A class designated as a separate identity composite key must follow these rules:
java.io.serializable
hashCode()
and equals()
methods@IdClass
) by calling EntityManager.find()
The consumer rewritten to support a composite primary key class is as follows:
@Entity @IdClass(Contact2PK.class) public class ContactConsumer2 implements Serializable { @Id private long contactId; @Id @Column(name = "FIRST_NAME", nullable = false) private String firstname; @Id @Column(name = "LAST_NAME", nullable = false) private String lastname; // Constructors omitted public long getContactId() {return contactId;} public void setContactId(long contactId) { this.contactId = contactId; } public String getFirstname() {return firstname;} public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() {return lastname;} public void setLastname(String lastname) { this.lastname = lastname; } // toString() method omitted }
In the class ContactConsumer1
, there are three fields that constitute the composite primary key: contactId
, firstname
, and lastname
. Entity uses the field annotations with @Id
. The class is annotated @IdClass
, which takes the single argument, the reference to the composite key class.
The composite key classContact2PK
is as follows:
package je7hb.basic2.jpa; import java.io.Serializable; public class Contact2PK implements Serializable { private long contactId; private String firstname; private String lastname; public long getContactId() {return contactId;} public void setContactId(long contactId) { this.contactId = contactId; } public String getFirstname() {return firstname;} public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() {return lastname;} public void setLastname(String lastname) { this.lastname = lastname; } // hashCode(), equals() method omitted // toString() method omitted }
Notice how, in the composite key class, the names and types of the fields as well as the property accessor match the same in the entity bean.
The hashCode()
and equals()
methods are omitted in the example, but they can be generated by any of the popular IDEs.
How are composite primary key classes used? The answer is that we can use the EntityManager.find()
method, which takes two arguments: the class of the entity that will be returned, and an instance of the composite key class.
An extract of a unit test method that illustrates the use case is as follows:
@PersistenctContext EntityManager em; public void shouldRetrieveByCompositeKey() { ContactConsumer2 consumer = new ContactConsumer2(100, "Annabel", "Smith") em.persist(consumer) ContactConsumer2 consumerCopy = em.find(ContactConsumer2.class, new Contact2PK(consumer.getContactId(), consumer.getFirstname(), consumer.getLastname())); assertEquals(consumer, consumerCopy) }
We create an entity bean, then we save it to the database using the entity manager. We search for the entity using a composite primary key instance. Finally, we check if the two entities are the same.
This second way of creating the composite primary keys relies on the embedded object instances into the entity bean. In this way, the fields and properties are delegated to the separate primary key object class. In order to delegate the primary key to a delegate class instance, use the annotation @javax.persistence.Embeddable
.
A class that is designated as embeddable must follow these rules:
@Embeddable
instead of @Entity
java.io.Serializable
hashCode()
and equals()
methodsThe contact primary key record is transformed into an embeddable entity as follows:
@Embeddable public class Contact3PK implements Serializable { private long contactId; @Column(name = "FIRSTNAME") private String firstname; @Column(name = "LASTNAME") private String lastname; public long getContactId() {return contactId;} public void setContactId(long contactId) { this.contactId = contactId; } public String getFirstname() {return firstname;} public void setFirstname(String firstname) { this.firstname = firstname; } public String getLastname() {return lastname;} public void setLastname(String lastname) { this.lastname = lastname; } public Contact3PK() { this(0,null,null); } public Contact3PK(long contactId, String firstname, String lastname) { this.contactId = contactId; this.firstname = firstname; this.lastname = lastname; } // A copy constructor public Contact3PK(Contact3PK ref) { this.contactId = ref.contactId; this.firstname = ref.firstname; this.lastname = ref.lastname; } // equals() and hashCode() methods omitted // toString() method omitted }
We first annotate the Contact3PK
class with @Embeddable
, which denotes to the JPA provider that this class is embeddable, a composite primary key instance.
It has exactly the same primary key columns as fields: contactId
, firstname
, and lastname
. We have also placed the @Column
metadata into the class now. We do not have to place the @Id
annotations on this type, because we made the class embeddable, and therefore this information is now implied.
Finally, embeddable must have correct and valid hashCode()
and equals()
methods in order for persistence to function successfully.
To conclude, we added a copy constructor to this composite key class, to make it easier for other developers to create copies of this entity in a test and production development work.
In order to make use of the embeddable class in an entity, the enclosing entity simply declares a reference field or the accessor property of the embeddable class, and annotates it with @javax.persistence.EmbeddedId
.
The contact consumer class is refactored to make use of the embeddable entity instance as follows:
packageje7hb.basic2.jpa; import javax.persistence.*; import java.io.Serializable; @Entity public class ContactConsumer3 implements Serializable { @EmbeddedId private Contact3PK contact; private String location; public ContactConsumer3() { this( new Contact3PK(), null); } public ContactConsumer3(Contact3PK contact, String location) { this.contact = contact; this.location = location; } public Contact3PK getContact() {return contact;} public void setContact(Contact3PK contact) { this.contact = contact; } public String getLocation() {return location;} public void setLocation(String location) { this.location = location; } // equals() and hashCode() methods omitted // toString() method omitted }
The entity bean, ContactConsumer3
, no longer defines separate primary key metadata. Instead, it delegates to Contact3PK
, the embeddable entity with the annotation @EmbeddedId
. Every annotation of @EmbeddedId
must reference a class that is marked up with @Embeddable
(or the corresponding ORM XML syntax).
We also threw in the additional field location to demonstrate that the entity bean can also still define additional columns of its own.
An extract of a unit test to demonstrate the principle of the embeddable entity objects is as follows:
@PersistenctContext EntityManager em; public void shouldRetrieveByEmbeddableKey() { ContactConsumer consumer = new ContactConsumer3(new Contact3PK(200, "Benjamin", "Ferguson"), "Glasgow")); em.persist(consumer) ContactConsumer3 consumerCopy = em.find(ContactConsumer3.class, new Contact3PK(consumer.getContact())); assertEquals(consumer, consumerCopy) }
We construct the embeddable Contact3PK
entity bean before we construct the entity bean ContactConsumer3
. We save it to the database using the entity manager. Next, we retrieve the same entity that was just saved with a new copy of the composite key object. This is exactly where a copy constructor is useful.
3.143.5.15