Hibernate 入門記 コレクションその10 idbag で many-to-many

今回はバッグのもう一つ,

  • <idbag> 要素

について学習します.
前回の <bag> 要素と何が違うのかというと... うーみゅ...
Hibernate では,複合キーはレガシー扱いで業務的な意味を持たない単なる ID (代理キーというらしい?)を主キーとすることを推奨しています.
しかしながら,関連テーブルについても代理キーを使うべきかは議論の余地がある,とのことです.実際,関連テーブルには代理キーは不要(二つの外部キーからなる複合キーを主キーにすればよい)です.
しかし,必要なら関連テーブルに代理キーを持たせることも出来て,そのマッピングに使うのが <idbag> ということらしいです...
なんてこった... やってもうた.(ToT)
確かにコレクションの始めの頃(<set> 要素の頃とか)は ID カラムを持たない関連テーブルを使っていましたが,いつの頃からかこの入門記では,関連テーブルにも ID カラムが当然のようについていました... 心より恥じる.
安易にコピペしたからだよなぁ.しかも何も疑問に思っていなかったかも.
あれ? そうでもないぞ.前回の <bag> 要素では,ID カラムがないと外部キーだけではユニークにならないんですけど? インデックスだけ作って主キーを付けなければよかったのか?
ともあれ,Hibernate に関連テーブルの主キーを扱ってもらうには,<idbag> 要素を使わなくてはならないようです.前回の場合,IDENTITY なカラムだったので HSQLDB が勝手に値を設定してくれていたというだけで,Hibernate はそんなカラムがあることさえしらなかったのですね.やっぱり心より恥じる.
次行きますよっ!次,次!(エビちゃん風)


ということで <idbag> 要素なんですが,属性は <bag> 要素と同じです.
内容としては,<key> 要素の前に

  • <collection-id> 要素

で関連テーブルの主キーに関するマッピング情報を記述します.
こいつの属性は次の通り.

column
主キーのカラム名を指定します.
type
主キーの型を指定します.

いずれも必須の属性です.
そして <collection-id> 要素の内容には,

  • <generator> 要素

を記述します.遠い昔(いつだ?)に学習した,主キーの値を生成するアレです.
なお,このドキュメントによると identity ジェネレータはサポートされていないとのことです.あらら.


ということでお試しします.
前回のテーブルにも ID のカラムはあったのですが,今回は IDENTITY の属性を外して Hibernate に値を設定してもらいましょう.
ということでテーブル定義はこんな感じ.

CREATE TABLE HAYAMIMI (
    ID INTEGER IDENTITY,
    WEEK VARCHAR
)

CREATE TABLE MODEL (
    ID INTEGER IDENTITY,
    NAME VARCHAR
)

CREATE TABLE APPEARANCE (
    ID INTEGER,
    WEEK INTEGER,
    MODEL INTEGER,
    PRIMARY KEY (ID)
)

そして早耳の永続クラスは前回と同じで,マッピングファイルを次のように修正.

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
    "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"
>
<hibernate-mapping auto-import="false" package="study">
    <class name="Hayamimi">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>

        <property name="week" access="field"/>
        <idbag name="model" access="field" table="appearance">
            <collection-id column="id" type="integer">
                <generator class="increment"/>
            </collection-id>
            <key column="week"/>
            <many-to-many column="model" class="Model"/>
        </idbag>
    </class>
</hibernate-mapping>

ジェネレータは面倒なので increment を使いました.
モデルの永続クラスとマッピングファイルは前回と同じ.
実行用のクラスも前回と同じでもいいのですが,週が変わってしまった(「今週」はもう「先週」)のが気になるので次のように修正.

package study;
import java.util.Iterator;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.cfg.Configuration;

public class Main {
    public static void main(String[] args) {
        try {
            Configuration config = new Configuration();
            SessionFactory factory = config.configure().buildSessionFactory();

            Session session = factory.openSession();

            Model asami = new Model("あさ美");
            session.save(asami);
            Model anne = new Model("杏");
            session.save(anne);
            Model kana = new Model("香奈");
            session.save(kana);
            Model sayo = new Model("紗世");
            session.save(sayo);
            Model jun = new Model("潤");
            session.save(jun);
            Model rosa = new Model("ローサ");
            session.save(rosa);

            Hayamimi week1 = new Hayamimi("先々週");
            week1.model.add(asami);
            week1.model.add(asami);
            week1.model.add(kana);
            week1.model.add(sayo);
            week1.model.add(asami);
            session.save(week1);

            Hayamimi week2 = new Hayamimi("先週");
            week2.model.add(rosa);
            week2.model.add(jun);
            week2.model.add(anne);
            week2.model.add(kana);
            week2.model.add(asami);
            session.save(week2);

            session.flush();

            session = factory.openSession();
            Iterator it = session.find("from study.Hayamimi").iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

これを実行!!

 onSave() : あさ美
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 杏
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 香奈
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 紗世
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 潤
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : ローサ
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 早耳トレンドNo1 先々週 [あさ美, あさ美, 香奈, 紗世, あさ美]
 Hibernate: insert into Hayamimi (week, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 早耳トレンドNo1 先週 [ローサ, 潤, 杏, 香奈, あさ美]
 Hibernate: insert into Hayamimi (week, id) values (?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: insert into appearance (week, id, model) values (?, ?, ?)
 Hibernate: select hayamimi0_.id as id, hayamimi0_.week as week from Hayamimi hayamimi0_
 onLoad() : 早耳トレンドNo1 先々週 net.sf.hibernate.collection.IdentifierBag@14e4e31
 onLoad() : 早耳トレンドNo1 先週 net.sf.hibernate.collection.IdentifierBag@1ef7de4
 Hibernate: select model0_.model as model__, model0_.week as week__, model0_.id as id__ from appearance model0_ where model0_.week=?
 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=?
 onLoad() : ローサ
 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=?
 onLoad() : 潤
 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=?
 onLoad() : 杏
 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=?
 onLoad() : 香奈
 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=?
 onLoad() : あさ美
 Hibernate: select model0_.model as model__, model0_.week as week__, model0_.id as id__ from appearance model0_ where model0_.week=?
 Hibernate: select model0_.id as id0_, model0_.name as name0_ from Model model0_ where model0_.id=?
 onLoad() : 紗世
 早耳トレンドNo1 先々週 net.sf.hibernate.collection.IdentifierBag@14e4e31
 早耳トレンドNo1 先週 net.sf.hibernate.collection.IdentifierBag@1ef7de4

ぐはぁっ,なんやねん IdentifierBag って?
別にいいけどさぁ,toString() ちゃんと実装してくれよぉ.
しょうがないので Hayamimi クラスの toString() で頑張ることにして再度実行!!(最後の所だけ)

 早耳トレンドNo1 先々週 [あさ美, あさ美, 香奈, 紗世, あさ美]
 早耳トレンドNo1 先週 [ローサ, 潤, 杏, 香奈, あさ美]

前回の実行結果と比べてみると,関連テーブルへの INSERT で前回は ID に値が設定されていなかったものが,今回は設定されるようになりました.めでたし,めでたし.


ということで,コレクションは一通り完了!! \(^o^)/
と思ったのもつかの間,いくつかやり損ねているものがあるようです.遅延ロードとか.心より恥じる.
ということで,次回は6章をきっちり締めくくれるように,これまでで取りこぼしたものをまとめて学習したいと思います.