Hibernate 入門記 コレクションその6 map で meny-to-many で index-many-to-many

引き続きマップの学習です.
前回書いたように,マップのキーは4種類あって,前回は <index> 要素を学習しました.となると,次は <composite-index> 要素かなぁと考えたのですが,あまり <index> 要素と変わり映えがしない感じ.単に複数のプロパティを持つ値型をキーに使えるというだけみたいです.
ということで,今回は

  • <index-many-to-many> 要素

にいってみましょう.
この要素が前回の <index> 要素と決定的に違うのは,マップのキーが値型ではなく,エンティティ(参照)型になるということです.
その属性は次の通り.

column
外部キーとなるカラム名を指定します.
class
エンティティのクラス名を指定します.

またしても外部キーが出てきました.これはもちろん,キーとなるエンティティへの外部キーです.
んー,これで終わり.かな?


えらくあっさりしてますが,しょうがないのでお試ししましょう.
とはいえ,キーが参照型になるような例って? キーになるということは,関連元にとってはそのエンティティはユニークなんですよね.うーみゅ.
まぁ,どのみち役に立つ例なんて書いたことがないので,今回もでたらめで行きましょう.
ドラマと女優の関連のキーに役名を使ってみます.

CREATE TABLE DRAMA (
    ID INTEGER IDENTITY,
    NAME VARCHAR
)

CREATE TABLE ROLE (
    ID INTEGER IDENTITY,
    NAME VARCHAR
)

CREATE TABLE ACTRESS (
    ID INTEGER IDENTITY,
    NAME VARCHAR
)

CREATE TABLE CASTING (
    ID INTEGER IDENTITY,
    DRAMA INTEGER,
    ROLE INTEGER,
    ACTRESS INTEGER
)

こうやって見ると,これは3項関連なんですね.おぉ,「6.9. Ternary Associations」の例で <index-many-to-many> を使ってますね.
これらのテーブルのうち,DRAMA,ROLE,ACTRESSに対応した永続クラスとマッピングファイルを作成します.大変だなぁ.
まずはドラマの永続クラス.

package study;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.Lifecycle;
import net.sf.hibernate.Session;

public class Drama implements Lifecycle {
    int id = -1;
    String name;
    Map actress;

    public Drama() {
    }
    public Drama(String name) {
        this.name = name;
        this.actress = new HashMap();
    }
    public String toString() {
        return name + " " + actress;
    }

    public void onLoad(Session s, Serializable id) {
        System.out.println("onLoad() : " + toString());
    }
    public boolean onSave(Session s) throws CallbackException {
        System.out.println("onSave() : " + toString());
        return false;
    }
    public boolean onUpdate(Session s) throws CallbackException {
        System.out.println("onUpdate() : " + toString());
        return false;
    }
    public boolean onDelete(Session s) throws CallbackException {
        System.out.println("onDelete() : " + toString());
        return false;
    }
}

次のこいつのマッピングファイル,study/Drama.hbm.xml

<?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="Drama">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>

        <property name="name" access="field"/>
        <map name="actress" access="field" table="CASTING">
            <key column="drama"/>
            <index-many-to-many column="role" class="Role"/>
            <many-to-many column="actress" class="Actress"/>
        </map>
    </class>
</hibernate-mapping>

<map> 要素の table 属性で関連テーブルを指定しています.そして <key> 要素で「こっち」への外部キーを,<index-many-to-many> 要素でマップのキーへの外部キーを,<many-to-many> 要素で「あっち」への外部キーを指定しています.
次に役名の永続クラス.

package study;
import java.io.Serializable;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.Lifecycle;
import net.sf.hibernate.Session;

public class Role implements Lifecycle {
    int id = -1;
    String name;

    public Role() {
    }
    public Role(String name) {
        this.name = name;
    }
    public String toString() {
        return name;
    }

    public void onLoad(Session s, Serializable id) {
        System.out.println("onLoad() : " + toString());
    }
    public boolean onSave(Session s) throws CallbackException {
        System.out.println("onSave() : " + toString());
        return false;
    }
    public boolean onUpdate(Session s) throws CallbackException {
        System.out.println("onUpdate() : " + toString());
        return false;
    }
    public boolean onDelete(Session s) throws CallbackException {
        System.out.println("onDelete() : " + toString());
        return false;
    }
}

そのマッピングファイル,study/Role.hbm.xml

<?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="Role">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>

        <property name="name" access="field"/>
    </class>
</hibernate-mapping>

そして女優の永続クラス.

package study;
import java.io.Serializable;
import net.sf.hibernate.CallbackException;
import net.sf.hibernate.Lifecycle;
import net.sf.hibernate.Session;

public class Actress implements Lifecycle {
    int id = -1;
    String name;

    public Actress() {
    }
    public Actress(String name) {
        this.name = name;
    }
    public String toString() {
        return name;
    }

    public void onLoad(Session s, Serializable id) {
        System.out.println("onLoad() : " + toString());
    }
    public boolean onSave(Session s) throws CallbackException {
        System.out.println("onSave() : " + toString());
        return false;
    }
    public boolean onUpdate(Session s) throws CallbackException {
        System.out.println("onUpdate() : " + toString());
        return false;
    }
    public boolean onDelete(Session s) throws CallbackException {
        System.out.println("onDelete() : " + toString());
        return false;
    }
}

そのマッピングファイル study/Actress.hbm.xml

<?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="Actress">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>

        <property name="name" access="field"/>
    </class>
</hibernate-mapping>

そして hibernate.cfg.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-configuration
    PUBLIC "-//Hibernate/Hibernate Configuration DTD 2.0//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd"
>
<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">org.hsqldb.jdbcDriver</property>
        <property name="connection.url">jdbc:hsqldb:hsql://localhost:9001</property>
        <property name="connection.username">sa</property>
        <property name="connection.password"></property>
        <property name="dialect">net.sf.hibernate.dialect.HSQLDialect</property>
        <property name="show_sql">true</property>

        <mapping resource="study/Drama.hbm.xml"/>
        <mapping resource="study/Role.hbm.xml"/>
        <mapping resource="study/Actress.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

最後に実行用のクラス.

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();

            Role mika = new Role("木本美香");
            session.save(mika);
            Role kimu = new Role("金優里");
            session.save(kimu);
            Role seri = new Role("鴨沢瀬理");
            session.save(seri);
            Role nazuna = new Role("鴨沢名津菜");
            session.save(nazuna);
            Role wakaba = new Role("塩田若葉");
            session.save(wakaba);

            Actress yukie = new Actress("Yukie Nakama");
            session.save(yukie);
            Actress akiko = new Actress("Akiko Yada");
            session.save(akiko);
            Actress aya = new Actress("Aya Ueto");
            session.save(aya);

            Drama tokyoWankei = new Drama("東京湾景");
            tokyoWankei.actress.put(mika, yukie);
            tokyoWankei.actress.put(kimu, yukie);
            session.save(tokyoWankei);

            Drama myLittleChef = new Drama("マイ・リトル・シェフ");
            myLittleChef.actress.put(seri, akiko);
            myLittleChef.actress.put(nazuna, aya);
            session.save(myLittleChef);

            Drama yamatoNadeshiko = new Drama("やまとなでしこ");
            yamatoNadeshiko.actress.put(wakaba, akiko);
            session.save(yamatoNadeshiko);

            session.flush();

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

ふーっ,なんかたいへん.
本当はリメイクとか続編のドラマで同じ役を違う女優が演じている例があるとよかったのですが,ちょっと思いつかなかったので... 無念だ.かわりに名津菜ちゃん初登場(いみふめ).
ともかく,実行!!

 onSave() : 木本美香
 Hibernate: insert into Role (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 金優里
 Hibernate: insert into Role (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 鴨沢瀬理
 Hibernate: insert into Role (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 鴨沢名津菜
 Hibernate: insert into Role (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 塩田若葉
 Hibernate: insert into Role (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Yukie Nakama
 Hibernate: insert into Actress (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Akiko Yada
 Hibernate: insert into Actress (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Aya Ueto
 Hibernate: insert into Actress (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : 東京湾景 {金優里=Yukie Nakama, 木本美香=Yukie Nakama}
 Hibernate: insert into Drama (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : マイ・リトル・シェフ {鴨沢瀬理=Akiko Yada, 鴨沢名津菜=Aya Ueto}
 Hibernate: insert into Drama (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : やまとなでしこ {塩田若葉=Akiko Yada}
 Hibernate: insert into Drama (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: insert into CASTING (drama, role, actress) values (?, ?, ?)
 Hibernate: select drama0_.id as id, drama0_.name as name from Drama drama0_
 Hibernate: select actress0_.actress as actress__, actress0_.drama as drama__, actress0_.role as role__ from CASTING actress0_ where actress0_.drama=?
 Hibernate: select actress0_.id as id0_, actress0_.name as name0_ from Actress actress0_ where actress0_.id=?
 onLoad() : Yukie Nakama
 Hibernate: select role0_.id as id0_, role0_.name as name0_ from Role role0_ where role0_.id=?
 onLoad() : 金優里
 Hibernate: select role0_.id as id0_, role0_.name as name0_ from Role role0_ where role0_.id=?
 onLoad() : 木本美香
 onLoad() : 東京湾景 {金優里=Yukie Nakama, 木本美香=Yukie Nakama}
 Hibernate: select actress0_.actress as actress__, actress0_.drama as drama__, actress0_.role as role__ from CASTING actress0_ where actress0_.drama=?
 Hibernate: select actress0_.id as id0_, actress0_.name as name0_ from Actress actress0_ where actress0_.id=?
 onLoad() : Akiko Yada
 Hibernate: select role0_.id as id0_, role0_.name as name0_ from Role role0_ where role0_.id=?
 onLoad() : 鴨沢瀬理
 Hibernate: select actress0_.id as id0_, actress0_.name as name0_ from Actress actress0_ where actress0_.id=?
 onLoad() : Aya Ueto
 Hibernate: select role0_.id as id0_, role0_.name as name0_ from Role role0_ where role0_.id=?
 onLoad() : 鴨沢名津菜
 onLoad() : マイ・リトル・シェフ {鴨沢瀬理=Akiko Yada, 鴨沢名津菜=Aya Ueto}
 Hibernate: select actress0_.actress as actress__, actress0_.drama as drama__, actress0_.role as role__ from CASTING actress0_ where actress0_.drama=?
 Hibernate: select role0_.id as id0_, role0_.name as name0_ from Role role0_ where role0_.id=?
 onLoad() : 塩田若葉
 onLoad() : やまとなでしこ {塩田若葉=Akiko Yada}
 東京湾景 {金優里=Yukie Nakama, 木本美香=Yukie Nakama}
 マイ・リトル・シェフ {鴨沢瀬理=Akiko Yada, 鴨沢名津菜=Aya Ueto}
 やまとなでしこ {塩田若葉=Akiko Yada}

...
いつものごとく,結果はOKなんですが,SELECT の乱発(12回!!)は目に余りますね...
outer-join 属性でちゃんと外部結合できるようにしないとなぁ.
ともあれ,<index-many-to-many> 要素を使うことが出来ました.まだ <index-many-to-any> 要素が残っているのですが,これは継承がらみと思われるのでいつものごとくスキップします.心より恥じる.
ということで,次回からはリストの学習に移ります.