Hibernate 入門記 継承その5 many-to-any

引き続き table per concrete class を使った場合の関連の学習,今回は多対多です.
前回の表で,多対多には <many-to-any> 要素を使うと書きましたが,実はそれに加えて <index-many-to-any> 要素を使うこともできるようです.こちらは関連をマップやリストにマッピングする際に使用します.
どちらにしても,使い方は <any> と同じようなものだとリファレンスに書いてあります.というか,それしか書いてありません.(;_;)
まぁ,同じといっているのだから同じなのでしょう.
多対多なので関連テーブルを使うわけですが,そこには当然,関連元と共に関連先への外部キーが含まれています.それに加えて,関連先のクラス (テーブル) を識別するためのカラムが必要になると思われます.


そしてマッピング.とりあえず

  • <many-to-any> 要素

を見ていきます.
DTD によると,こいつは次の属性を持っています.

id-type
外部キーの型を指定します.必須です.
meta-type
関連先のクラスを示すカラムの型を指定します.
省略すると Hibernate 型の class になると思われます.

これだけです.<any> 要素にあったその他の属性は,<many-to-any> 要素の親である <set> などで指定するので,こんなもんでいいのでしょう.たぶん.
こいつの内容には,<any> 要素のように,関連先の型を示すカラム (1つ) と外部キーのカラム(一つ以上)を記述します.
これだけ? かな?


ということでお試しタイムです.今回はネタを微妙に変更します.
まずはモデル (雑誌モデルのモデル) があって,これには継承を使いません.
そして,モデルは出演するメディアと多対多の関連を持ちます.メディアは抽象クラスで,雑誌と CM というサブクラスがあります.これは当然 table per concrete class でマッピングされます.
ということでテーブル定義です.

CREATE TABLE MODEL (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR
)
CREATE TABLE MAGAZINE (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR
)
CREATE TABLE CM (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR
)
CREATE TABLE MODEL_MEDIA (
    MODEL INTEGER,
    MEDIA INTEGER,
    MEDIA_TYPE VARCHAR,
    PRIMARY KEY(MODEL, MEDIA, MEDIA_tYPE)
)

メディアは抽象クラスなのでテーブルはありません.関連テーブル MODEL_MEDIA には,二つのテーブルへの外部キーと共にメディアのクラスを識別するカラムがあります.
次にモデルの永続クラス.

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

public class Model implements Lifecycle {
    int id = -1;
    String name;
    Set media;

    public Model() {
    }
    public Model(String name) {
        this.name = name;
        this.media = new HashSet();
    }
    public String toString() {
        return name + " : " + media;
    }

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

特にどうと言うこともありません.
そしてモデルのマッピングファイル,study/Model.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="Model">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name" access="field"/>
        <set name="media" access="field" table="MODEL_MEDIA">
            <key column="MODEL"/>
            <many-to-any id-type="integer">
                <column name="MEDIA_TYPE"/>
                <column name="MEDIA"/>
            </many-to-any>
        </set>
    </class>
</hibernate-mapping>

続いてメディアのクラス.

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

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

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

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

こいつにはモデルとの関連を持たせていないことに注意.理由は後述.
メディアは抽象クラスなのでマッピングファイルはありません.
そしてメディアのサブクラス,雑誌の永続クラスです.

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

public class Magazine extends Media {
    public Magazine() {
    }
    public Magazine(String name) {
        super(name);
    }
}

雑誌のマッピングファイル,study/Magazine.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="Magazine">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name" access="field"/>
    </class>
</hibernate-mapping>

そしてもう一つのメディアのサブクラス,CM クラスです.

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

public class Cm extends Media {
    public Cm() {
    }
    public Cm(String name) {
        super(name);
    }
}

CM のマッピングファイル,study/Cm.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="Cm">
        <id name="id" access="field" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name" access="field"/>
    </class>
</hibernate-mapping>

ふーっ,結構大変...
三つのマッピングファイルを hibernate.cfg.xml に記述します.
そして実行用のクラス.

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

            Magazine cancam = new Magazine("CanCam");
            session.save(cancam);
            Magazine vivi = new Magazine("ViVi");
            session.save(vivi);
            Magazine classy = new Magazine("CLASSY");
            session.save(classy);

            Cm aviva = new Cm("Aviva");
            session.save(aviva);
            Cm ampm = new Cm("am/pm");
            session.save(ampm);

            Model yuri = new Model("Yuri Ebihara");
            yuri.media.add(cancam);
            yuri.media.add(aviva);
            session.save(yuri);

            Model asami = new Model("Asami Usuda");
            asami.media.add(cancam);
            session.save(asami);

            Model sayo = new Model("Sayo Aizawa");
            sayo.media.add(vivi);
            sayo.media.add(classy);
            sayo.media.add(ampm);
            session.save(sayo);

            session.flush();
            session.connection().commit();

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

うー,結構面倒でした.
ともあれ,実行!!!!

 onSave() : CanCam
 Hibernate: insert into Magazine (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : ViVi
 Hibernate: insert into Magazine (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : CLASSY
 Hibernate: insert into Magazine (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Aviva
 Hibernate: insert into Cm (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : am/pm
 Hibernate: insert into Cm (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Yuri Ebihara
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Asami Usuda
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Sayo Aizawa
 Hibernate: insert into Model (name, id) values (?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: insert into MODEL_MEDIA (MODEL, MEDIA_TYPE, MEDIA) values (?, ?, ?)
 Hibernate: select model0_.id as id, model0_.name as name from Model model0_
 onLoad() : Yuri Ebihara
 onLoad() : Asami Usuda
 onLoad() : Sayo Aizawa
 Hibernate: select media0_.MEDIA_TYPE as MEDIA_TYPE__, media0_.MEDIA as MEDIA__, 
                media0_.MODEL as MODEL__ 
            from MODEL_MEDIA media0_ where media0_.MODEL=?
 Hibernate: select cm0_.id as id0_, cm0_.name as name0_ from Cm cm0_ where cm0_.id=?
 onLoad() : am/pm
 Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ 
            from Magazine magazine0_ where magazine0_.id=?
 onLoad() : ViVi
 Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ 
            from Magazine magazine0_ where magazine0_.id=?
 onLoad() : CLASSY
 Hibernate: select media0_.MEDIA_TYPE as MEDIA_TYPE__, media0_.MEDIA as MEDIA__, 
                        media0_.MODEL as MODEL__ 
            from MODEL_MEDIA media0_ where media0_.MODEL=?
 Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ 
            from Magazine magazine0_ where magazine0_.id=?
 onLoad() : CanCam
 Hibernate: select media0_.MEDIA_TYPE as MEDIA_TYPE__, media0_.MEDIA as MEDIA__, 
                media0_.MODEL as MODEL__ 
            from MODEL_MEDIA media0_ where media0_.MODEL=?
 Hibernate: select cm0_.id as id0_, cm0_.name as name0_ from Cm cm0_ where cm0_.id=?
 onLoad() : Aviva
 Yuri Ebihara : [CanCam, Aviva]
 Asami Usuda : [CanCam]
 Sayo Aizawa : [CLASSY, am/pm, ViVi]

うーみゅ,うまくいったのか? できてるっぽい.なんか大丈夫みたいです.目出度し,目出度し.
なーんちって.本当は単なる手抜きです.m(__)m
今回,メディアの方にはモデルへの関連を持たせていません.最初は双方向にしようかと思ったのですが,マッピングの指定がよく分からなかったのです.
というのも,これまで見てきた <any> 要素や <many-to-any> 要素の使い方は,いずれも関連先のクラスが継承を使っていました.
しかし,双方向にするとなると継承を使ったクラスも関連元になるわけですが,その場合のマッピングについてはリファレンスを見ても説明が見あたりません.できるのかできないのかさえよく分からないという...心より恥じる.
本来なら,関連元も関連先も継承を使った場合の双方向マッピングを探求すべきという気がしないでもありませんが,実は table per concrete class を使うのはきわめてレアケースとのことです.よって,双方向の探求もやめておきます.だって,他の二つの継承マッピングを使えば,普通に双方向の多対多もできるはずなので...


ということで,後味の悪さを残しつつも「8. Inheritance Mapping」を終わりにします.
次回からは「9. Manipulating Persistent Data」へ進みます.ようやく Session の使い方の学習です!
でもその前に,さぼりっぱなしになっている EJB 3.0 (Early Draft) 入門記を進めようかと思う今日この頃でした.