複合PK

今回は複合PKです。

【前提条件】

[環境]
[その他]
  • JPAの設定が完了している*1

【概要】

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>

【実行】

作成したXHTMLにアクセスします。

IDクラス・組み込みIDクラスの両方で
データが取得できました。