JPA + EJB + JSF による Web アプリケーション(その 6)

エンティティ間の結合を表現する

現在サンプルとして使用しているデータベースにおいて、担当者マスタテーブルには MGR_ID というカラムがあります。これは外部キーとして同じ担当者マスタテーブルの担当者IDカラムを参照しており、上司である担当者の担当者IDを入力することになっています。これを JPA で表現してみましょう。

package jp.mydns.akanekodou.entity;

import java.io.Serializable;
import java.util.Date;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.OneToMany;
import javax.persistence.ManyToOne;
import javax.persistence.JoinColumn;

@Entity
@Table(name = "担当者マスタ")
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "担当者ID")
    @GeneratedValue
    private int id;
    @Column(name = "担当者名")
    private String name;
    @Column(name = "ふりがな")
    private String phonetic;
    @ManyToOne
    @JoinColumn(name = "MGR_ID", referencedColumnName="担当者ID")
    private Employee manager;
    @OneToMany(mappedBy = "manager", fetch = FetchType.EAGER)
    private List<Employee> assistants;
    @Column(name = "生年月日")
    private Date birthday;
    @Column(name = "性別")
    private int sex;

    public Employee() { }

    public Employee(
        String name,
        String phonetic,
        Employee manager,
        Date birthday,
        int sex
    ) {
        this.name = name;
        this.phonetic = phonetic;
        this.manager = manager;
        this.birthday = birthday;
        this.sex = sex;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhonetic() {
        return phonetic;
    }

    public void setPhonetic(String phonetic) {
        this.phonetic = phonetic;
    }

    public Employee getManager() {
        return manager;
    }

    public List<Employee> getAssistants() {
        return assistants;
    }

    public void setAssistants(List<Employee> assistants) {
        this.assistants = assistants;
    }

    public void setManager(Employee manager) {
        this.manager = manager;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public int getSex() {
        return sex;
    }

    public void setSex(int sex) {
        this.sex = sex;
    }
}

ここで OneToMany, ManyToOne という新しいアノテーションが登場しました。上司一人に対して部下数人という関係ですので上司を示すプロパティに ManyToOne を、部下を示すプロパティに OneToMany を使います(1 対 1 の場合は OneToOne を使います)。結合の方法を明示するために JoinColumn を使います。今回は MGR_ID カラムを使って、担当者IDカラムを参照して Employee エンティティを「ぶら下げる」形になります。EmployeeEmployee 型をプロパティとして持つというちょっと奇妙な格好ですが、自己参照ですので必然的にこうなります。

OneToManymappedBy は、参照される側のプロパティを示します。この場合は部下の List ですから参照されるのは上司(manager)ですね。fetch はデフォルトの LAZY ではなく EAGER にします((LAZY では実際に参照される段階の時に関連エンティティのリストを取得することになるため、セッションが閉じてから参照を試みるとエラーになる。))。

エンティティを作成したら persistence.xml を修正して、Employee を追加しておきましょう。

EJB による DAO 層の実装

続いて DAO 層を EJB で実装します。

package jp.mydns.akanekodou.dao;

import javax.ejb.Local;

import java.util.List;

import jp.mydns.akanekodou.entity.Employee;

@Local
public interface EmployeeDAO {
    List<Employee> all();
}
package jp.mydns.akanekodou.dao;

import javax.ejb.Stateless;

import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;
import javax.persistence.Query;

import java.util.List;

import jp.mydns.akanekodou.entity.Employee;

@Stateless
public class EmployeeDAOImpl implements EmployeeDAO {
    @PersistenceContext
    EntityManager manager;

    @Override
    public List<Employee> all() {
        Query query = manager.createQuery("from Employee");
        @SuppressWarnings("unchecked")
        List<Employee> result = query.getResultList();
        return result;
    }
}

今回は全件取得メソッドのみ実装しました。

表示用の Facelets と管理 Bean の作成は次回に。