Moving further along with entity beans

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.

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

Expanding the @Table annotation

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

UniqueConstraint[]

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

Index[]

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

Mapping the primary keys

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.

The single primary key

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.

Composite primary keys

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.

Using the @IdClass annotation

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:

  • An IdClass, a composite key class, must have the following features:
    • It must implement the marker interface java.io.serializable
    • It must be public with a no-arguments default constructor
    • It must have valid hashCode() and equals() methods
  • The fields in the composite key class must correspond with exactly the same as the fields or property accessor in the entity
  • The matching entity bean can be found with an instance of the composite key class (@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.

Using the @Embeddable annotation

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:

  • The embeddable entity beans are annotated with @Embeddable instead of @Entity
  • The embeddable entities must implement the marker interface java.io.Serializable
  • It must have valid hashCode() and equals() methods
  • The embeddable classes are special entities that only exist as part of another entity
  • An embeddable class may have the single value attributes or collection of the values, each of which must be following the general rules of the JPA specification
  • An embeddable entity can also contain other embeddable classes to represent their state
  • An embeddable entity may contain the relationships to other entities, including other embeddable entities

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

Using the @EmbeddedId annotation

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.5.15