複合PK
今回は複合PKです。
【前提条件】
[環境]
- JDK 1.7.0_07
- Glassfish 3.1.2.2
- PostgreSQL 9.1(JDBC:postgresql-9.1-901.jdbc4.jar)
【概要】
EntityManager#findメソッドのキー値は一つだけしか受け付けません。
そのため、複合PKを使用している場合、
エンティティクラスだけでは対応できません。
JPA2.0ではキー用のクラスを作成して、
それをキー値として使用できるようになっています。
キー用のクラスは
IDクラスの指定か組み込みIDクラスを使用します。
【サンプルコード】
[テーブル]
今回のサンプルのテーブルは「sales」テーブルを作成します。
CREATE TABLE sales ( user_id character varying(10) NOT NULL, goods_code character varying(5) NOT NULL, number_of_articles integer, CONSTRAINT sales_primary_key PRIMARY KEY (user_id , goods_code ) )
ユーザIDと商品コードをキーに個数を管理するテーブルです。
売り上げテーブルをイメージしています。
[IDクラスの指定(Entityクラス)]
まずはIDクラスを指定する方法です。
エンティティクラスの名前は「Sales1」とします。
package jp.glory.persistence.entity; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.IdClass; import javax.persistence.Table; import jp.glory.persistence.key.SalesKeyIdClass; @Entity @Table(name = "sales") @IdClass(value = SalesKeyIdClass.class) public class Sales1 implements Serializable { private static final long serialVersionUID = 4677612728077605784L; @Id @Column(name = "user_id") private String userId = null; @Id @Column(name = "goods_code") private String goodsCode = null; @Column(name = "number_of_articles") private int numberOfArticles = 0; // アクセサメソッドは省略 }
IdアノテーションがuserIdとgoodsCodeについています。
IDクラスを指定するのにはjavax.persistence.IdClassアノテーションを使用します。
IdClassアノテーションはvalue属性を持ち、
IDクラスのクラスを指定します。
[IDクラスの指定(IDクラス)]
IDクラスは普通のPOJOとして作成します。
package jp.glory.persistence.key; public class SalesKeyIdClass { private String userId = null; private String goodsCode = null; // アクセサメソッドは省略 }
IDクラスは普通のPOJOですが、一つだけ制約があります。
IDクラスが持つプロパティと
エンティティクラスのキーとなるプロパティ、
この二つの名前と型が一致している必要があります。
[組み込みIDクラス(Entityクラス)]
続いて組み込みIDクラスです。
組み込みIDクラスはエンティティクラスのキーとして、
組み込みIDクラス自身を持たせることができます。
package jp.glory.persistence.entity; import java.io.Serializable; import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.Table; import jp.glory.persistence.key.EmbeddedIdSalesKey; @Entity @Table(name = "sales") public class Sales2 implements Serializable { private static final long serialVersionUID = 4677612728077605784L; @EmbeddedId private EmbeddedIdSalesKey key = null; @Column(name = "number_of_articles") private int numberOfArticles = 0; // アクセサメソッドは省略 }
組み込みIDクラスを使用するには
javax.persistence.EmbeddedIdアノテーションを使用します。
EmbeddedIdアノテーションは属性を持たないアノテーションなので、
キーとなるプロパティにアノテーションをつけるだけです。
[組み込みIDクラス(組み込みIDクラス)]
組み込みIDクラスはIDクラスと同じくPOJOです。
package jp.glory.persistence.key; import javax.persistence.Column; import javax.persistence.Embeddable; @Embeddable public class EmbeddedIdSalesKey { @Column(name = "user_id") private String userId = null; @Column(name = "goods_code") private String goodsCode = null; @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((goodsCode == null) ? 0 : goodsCode.hashCode()); result = prime * result + ((userId == null) ? 0 : userId.hashCode()); return result; } @Override public boolean equals(final Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; EmbeddedIdSalesKey other = (EmbeddedIdSalesKey) obj; if (goodsCode == null) { if (other.goodsCode != null) return false; } else if (!goodsCode.equals(other.goodsCode)) return false; if (userId == null) { if (other.userId != null) return false; } else if (!userId.equals(other.userId)) return false; return true; } // アクセサメソッドは省略 }
組み込みIDクラスには
javax.persistence.Embeddableアノテーションをつけます。
Embeddableアノテーションは属性を持たないアノテーションなので、
クラスにアノテーションをつけるだけです。
組み込みIDクラスではColumnアノテーションで
カラムの設定を行います。
キーの比較を行うのでequalsメソッドとhashCodeメソッドを
オーバーライドして実装する必要があります。
[レコードの取得]
つづいてエンティティを取得する処理です。
package jp.glory.ui; import javax.faces.bean.ManagedBean; import javax.faces.bean.RequestScoped; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; import jp.glory.persistence.entity.Sales1; import jp.glory.persistence.entity.Sales2; import jp.glory.persistence.key.EmbeddedIdSalesKey; import jp.glory.persistence.key.SalesKeyIdClass; @ManagedBean(name = "salesKey") @RequestScoped public class SalesKeyPage { private Sales1 sales1 = null; private Sales2 sales2 = null; public SalesKeyPage() { EntityManagerFactory factory = Persistence.createEntityManagerFactory("blog"); EntityManager manager = factory.createEntityManager(); SalesKeyIdClass key1 = new SalesKeyIdClass(); key1.setUserId("USER-0001"); key1.setGoodsCode("AB-01"); sales1 = manager.find(Sales1.class, key1); EmbeddedIdSalesKey key2 = new EmbeddedIdSalesKey(); key2.setUserId("USER-0002"); key2.setGoodsCode("CD-02"); sales2 = manager.find(Sales2.class, key2); } // アクセサメソッドは省略 }
サンプルではfindメソッドに
IDクラスと組み込みIDクラスを指定してエンティティを取得しています。
[persistence.xml]
persistence.xmlにはEntityアノテーションをつけたクラスのみの指定で動きます。
<?xml version="1.0" encoding="UTF-8"?> <persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"> <persistence-unit name="blog" transaction-type="JTA"> <jta-data-source>jdbc/blog</jta-data-source> <class>jp.glory.persistence.entity.Sales1</class> <class>jp.glory.persistence.entity.Sales2</class> </persistence-unit> </persistence>
[ページ]
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:f="http://java.sun.com/jsf/core" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html"> <f:view> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>キーサンプル</title> </h:head> <h:body> <table border="1"> <tr> <th> ユーザID </th> <td> <h:outputText value="#{salesKey.sales1.userId}" /> </td> <td> <h:outputText value="#{salesKey.sales2.key.userId}" /> </td> </tr> <tr> <th> 商品コード </th> <td> <h:outputText value="#{salesKey.sales1.goodsCode}" /> </td> <td> <h:outputText value="#{salesKey.sales2.key.goodsCode}" /> </td> </tr> <tr> <th> 個数 </th> <td> <h:outputText value="#{salesKey.sales1.numberOfArticles}" /> </td> <td> <h:outputText value="#{salesKey.sales2.numberOfArticles}" /> </td> </tr> </table> </h:body> </f:view> </html>