2.2.5.3. Collections コレクション

<2.2.5.3. Collections | 目次 | 2.2.5.3.3. Many-to-many>

2.2.5.3.1. Overview 概要

Collection、List(順序ありでもなしでも)、Map、Set にマッピングできるよ。EJB3 仕様では、@javax.persistence.OrderBy アノテーションを使って順序つきリストにマップする仕方を定義しているよ。このアノテーションは、順序をつけるためのカンマで区切られた(対象エンティティの)プロパティのリストをパラメータに取るよ。(たとえば、firstname asc、age desc)この指定が空の場合、id で並べられるよ。インデックスコレクションの詳細は、Hibernate Annotation Extensions を参照してね。EJB3 は @MapKey(name="myProperty") を使って、対象エンティティのプロパティをキーとしたマップにマッピングすることもできるよ。(myPropertyは対象エンティティのプロパティ名) プロパティ名なしで @MapKey を使うと主キーが使われるよ。マップのキーはプロパティが指しているのと同じカラムを使うよ。マップキーを持つカラムがないと、マップキーは対象のプロパティをあらわすよ*1。一度だけ読み込まれるということを知っておいて。プロパティの値を変更したら、キーはもうプロパティと同期してないよ。キーは自動的には更新されないよ。(詳細は Hibernate Annotation Extensions を見てね) 多くの人が と @MapKey を混乱してるよ。これらは 別の二つの機能だよ。@MapKey はまだ少し限定的だから、詳しくは JIRAトラッキングシステムのフォーラムをチェックしてみてね。

Hibernate にはいくつかのコレクションの概念があるよ。

Table 2.1. Collections semantics コレクションの意味
意味 java表現 アノテーション
袋の意味 java.util.List, java.util.Collection @org.hibernate.annotations.CollectionOfElements or @OneToMany or @ManyToMany
主キー付きの袋 java.util.List, java.util.Collection (@org.hibernate.annotations.CollectionOfElements or @OneToMany or @ManyToMany) and @CollectionId
リスト java.util.List (@org.hibernate.annotations.CollectionOfElements or @OneToMany or @ManyToMany) and @org.hibernate.annotations.IndexColumn
集合 java.util.Set @org.hibernate.annotations.CollectionOfElements or @OneToMany or @ManyToMany
写像 java.util.Map (@org.hibernate.annotations.CollectionOfElements or @OneToMany or @ManyToMany) and (nothing or @org.hibernate.annotations.MapKey/MapKeyManyToMany for true map support, OR @javax.persistence.MapKey

特に、 @org.hibernate.annotations.IndexColumn を含まない java.util.List は、袋と考えられているよ。

プリミティブ、コアタイプ、または埋め込みオブジェクトのコレクションは EJB3 ではサポートされてないけど、Hibernate ではできるよ。(Hibernate Annotation Extensions 参照)

@Entity public class City {
    @OneToMany(mappedBy="city")
    @OrderBy("streetName")
    public List<Street> getStreets() {
        return streets;
    }
...
}

@Entity public class Street {
    public String getStreetName() {
        return streetName;
    }

    @ManyToOne
    public City getCity() {
        return city;
    }
    ...
}


@Entity
public class Software {
    @OneToMany(mappedBy="software")
    @MapKey(name="codeName")
    public Map<String, Version> getVersions() {
        return versions;
    }
...
}

@Entity
@Table(name="tbl_version")
public class Version {
    public String getCodeName() {...}

    @ManyToOne
    public Software getSoftware() { ... }
...
}

コレクションがロードされたときに、City は streetName でソートされた Street のコレクションを持つよ。Software は codeName をキーにした Version のマップを持つよ。

コレクションがジェネリックじゃないなら、targetEntity を定義しないといけないよ。これは、ターゲットエンティティのクラス名を取る、アノテーションの属性だよ。

2.2.5.3.2. One-to-many 一対多

一対多関連は @OneToMany アノテーションでプロパティレベルに定義されるよ。一対多関連は双方向かもしれないよね。

2.2.5.3.2.1. Bidirectional 双方向

EJB3仕様において、多対一は大体双方向関連の親側なので、一対多関連は @OneToMany(mappedBy=...) でアノテートされるよ。

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
}

Troop は Solidier の troop プロパティ で、双方向の一対多関連を持っているよ。mappedBy 側に物理マッピングを定義してはいけないよ。

親側のような一対多側で、双方向の一対多をマッピングするために、mappedBy を除いて、多対一側の @JoinClumn の insertable、updatable を false にしないといけないよ。この方法は、明らかに最適ではなくて、同じ 追加のUPDATE分を生成するよ。

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}
2.2.5.3.2.2. Unidirectional 単方向

親エンティティの外部キーを使った単方向の一対多は、一般的じゃなくて、ほんとにお勧めしないよ。このような場合は、次で説明する結合テーブルを使うことを強くお勧めするよ。この種類の関連は @JoinColumn で定義するよ。

@Entity
public class Customer implements Serializable {
    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
    @JoinColumn(name="CUST_ID")
    public Set<Ticket> getTickets() {
    ...
}

@Entity
public class Ticket implements Serializable {
    ... //no bidir
}

Customer は、結合カラム CUST_ID を使って、Ticket へ一方向の関連を記述しているよ。

2.2.5.3.2.3. Unidirectional with join table 結合テーブルを使った単方向

結合テーブルを使った単方向一対多が好ましいよ。この関連は @JoinTable を使うよ。

@Entity
public class Trainer {
    @OneToMany
    @JoinTable(
            name="TrainedMonkeys",
            joinColumns = @JoinColumn( name="trainer_id"),
            inverseJoinColumns = @JoinColumn( name="monkey_id")
    )
    public Set<Monkey> getTrainedMonkeys() {
    ...
}

@Entity
public class Monkey {
    ... //no bidir
}

Trainer は、TrainedMonkeys結合テーブル(Trainerへのtrainer_id外部キーと、Monkeyへの外部キー monky_idをもつ)を使って Monkey に一方向関連を記述しているよ。

2.2.5.3.2.4. Defaults デフォルト

物理マッピングが何もないと、結合テーブルによる一方向一対多が使われるよ。テーブル名は 親テーブル名、_、もう一方のテーブル名を繋げたものになるよ。親テーブルを参照する外部キー名は、親テーブル、_、親テーブルの主キー名を連結したものになるよ。もう一方のテーブルを参照する外部キー名は、親プロパティ名、_、もう一方のテーブルの主キー名になるよ。もう一方のテーブルを参照する外部キーにユニーク制約がつけられるよ。

@Entity
public class Trainer {
    @OneToMany
    public Set<Tiger> getTrainedTigers() {
    ...
}

@Entity
public class Tiger {
    ... //no bidir
}

Trainer は Trainer_Tiger結合テーブル(Trainer をさす trainer_id 外部キーと、Monkey をさす trainedTigers_id 外部キーをもつ )を使って、Tiger へ単方向関連を定義するよ。<2.2.5.3. Collections | 目次 | 2.2.5.3.3. Many-to-many>

*1:意味不明訳・・・