Java EE 8 Application Development
上QQ阅读APP看书,第一时间看更新

Composite primary keys

Most tables in the CUSTOMERDB database have a column with the sole purpose of serving as a primary key (this type of primary key is sometimes referred to as a surrogate primary key or as an artificial primary key). However, some databases are not designed this way; instead a column in the database that is known to be unique across rows is used as the primary key. If there is no column whose value is not guaranteed to be unique across rows, then a combination of two or more columns is used as the table's primary key. It is possible to map this kind of primary key to JPA entities by using a primary key class.

There is one table in the CUSTOMERDB database that does not have a surrogate primary key: the ORDER_ITEMS table. This table serves as a join table between the ORDERS and the ITEMS tables, in addition to having foreign keys for these two tables, this table has an additional column called ITEM_QTY ;this column stores the quantity of each item in an order. Since this table does not have a surrogate primary key, the JPA entity mapping to it must have a custom primary key class. In this table, the combination of the ORDER_ID and the ITEM_ID columns must be unique, therefore this is a good combination for a composite primary key:

package net.ensode.javaee8book.compositeprimarykeys.entity; 
 
import java.io.Serializable; 
 
public class OrderItemPK implements Serializable 
{ 
  public Long orderId; 
  public Long itemId; 
 
  public OrderItemPK() 
  { 
 
  } 
 
  public OrderItemPK(Long orderId, Long itemId) 
  { 
    this.orderId = orderId; 
    this.itemId = itemId; 
  } 
 
  @Override 
  public boolean equals(Object obj) 
  { 
    boolean returnVal = false; 
 
    if (obj == null) 
    { 
      returnVal = false; 
    } 
    else if (!obj.getClass().equals(this.getClass())) 
    { 
      returnVal = false; 
    } 
    else 
    { 
      OrderItemPK other = (OrderItemPK) obj; 
 
      if (this == other) 
      { 
        returnVal = true; 
      } 
      else if (orderId != null && other.orderId != null 
          && this.orderId.equals(other.orderId)) 
      { 
        if (itemId != null && other.itemId != null 
            && itemId.equals(other.itemId)) 
        { 
          returnVal = true; 
        } 
      } 
      else 
      { 
        returnVal = false; 
      } 
    } 
 
    return returnVal; 
  } 
 
  @Override 
  public int hashCode() 
  { 
    if (orderId == null || itemId == null) 
    { 
      return 0; 
    } 
    else 
    { 
      return orderId.hashCode() ^ itemId.hashCode(); 
    } 
  } 
} 

A custom primary key class must satisfy the following requirements:

  • The class must be public
  • It must implement java.io.Serializable
  • It must have a public constructor that takes no arguments
  • Its fields must be public or protected
  • Its field names and types must match those of the entity
  • It must override the default hashCode() and equals() methods defined in the java.lang.Object class

The preceding OrderPK class meets all of these requirements. It also has a convenience constructor that takes two Long objects to initialize its orderId and itemId fields. This constructor was added for convenience; this is not a prerequisite for the class to be used as a primary key class.

When an entity uses a custom primary key class, it must be decorated with the @IdClass annotation. Since the OrderItem class uses OrderItemPK as its custom primary key class, it must be decorated with the aforementioned annotation:

package net.ensode.javaee8book.compositeprimarykeys.entity; 
 
import javax.persistence.Column; 
import javax.persistence.Entity; 
import javax.persistence.Id; 
import javax.persistence.IdClass; 
import javax.persistence.Table; 
 
@Entity 
@Table(name = "ORDER_ITEMS") 
@IdClass(value = OrderItemPK.class) 
public class OrderItem 
{ 
  @Id 
  @Column(name = "ORDER_ID") 
  private Long orderId; 
 
  @Id 
  @Column(name = "ITEM_ID") 
  private Long itemId; 
 
  @Column(name = "ITEM_QTY") 
  private Long itemQty; 
 
  public Long getItemId() 
  { 
    return itemId; 
  } 
 
  public void setItemId(Long itemId) 
  { 
    this.itemId = itemId; 
  } 
 
  public Long getItemQty() 
  { 
    return itemQty; 
  } 
 
  public void setItemQty(Long itemQty) 
  { 
    this.itemQty = itemQty; 
  } 
 
  public Long getOrderId() 
  { 
    return orderId; 
  } 
 
  public void setOrderId(Long orderId) 
  { 
    this.orderId = orderId; 
  } 
} 

There are two differences between the preceding entity and the previous entities we have seen. The first difference is that this entity is decorated with the @IdClass annotation, indicating the primary key class corresponding to it. The second difference is that the preceding entity has more than one field decorated with the @Id annotation. Since this entity has a composite primary key, each field that is part of the primary key must be decorated with this annotation.

Obtaining a reference of an entity with a composite primary key is not much different from obtaining a reference to an entity with a primary key consisting of a single field. The following example demonstrates how to do this:

package net.ensode.javaee8book.compositeprimarykeys.namedbean; 
 
import javax.enterprise.context.RequestScoped; 
import javax.inject.Named; 
import javax.persistence.EntityManager; 
import javax.persistence.PersistenceContext; 
import net.ensode.javaee8book.compositeprimarykeys.entity.OrderItem; 
import net.ensode.javaee8book.compositeprimarykeys.entity.OrderItemPK; 
 
@Named 
@RequestScoped 
public class CompositePrimaryKeyDemoBean { 
 
    @PersistenceContext 
    private EntityManager entityManager; 
 
    private OrderItem orderItem; 
 
    public String findOrderItem() { 
        String retVal = "confirmation"; 
 
        try { 
            orderItem = entityManager.find(OrderItem.class,                                                                   
new OrderItemPK(1L, 2L));
} catch (Exception e) { retVal = "error"; e.printStackTrace(); } return retVal; } public OrderItem getOrderItem() { return orderItem; } public void setOrderItem(OrderItem orderItem) { this.orderItem = orderItem; } }

As can be seen in this example, the only difference between locating an entity with a composite primary key and an entity with a primary key consisting of a single field is that an instance of the custom primary key class must be passed as the second argument of the EntityManager.find() method. Fields for this instance must be populated with the appropriate values for each field that is part of the primary key.