Hibernate 入門記 継承その4 any 要素

前回までに学習した継承マッピングのうち,最後に学習した table per concrete class を使った場合には,その永続クラスへの関連に大きな制限があるようです.
まずは,「8.2. Limitations」より,各継承マッピングと扱うことのできる関連を見てみましょう.

マッピング方式1対11対多多対1多対多
table per class hierarchy<one-to-one><one-to-many><many-to-one><many-to-many>
table per subclass
table per concrete class未サポート未サポート<any><many-to-any>


この表は,ある永続クラス (関連元) が,継承を使った永続クラス(関連先)への関連を持つ場合に使う要素を示しています.
最初の二つのマッピング方式を使った場合は,これまで学習したとおりに関連を扱うことができます.
しかし,table per concrete class の場合には,1対1および1対多の関連はサポートされません.残念!!!!
多対1および多対多の関連を扱うことは出来ますが,そのために使う要素はこれまでスキップしまくってきたものです.よって、切腹!!!!
ということで,今回は手始めに多対1の関連について学習します.情報源は主に「5.2.5. Any type mappings」です.


まずはテーブルについて.
多対1の関連ということは,多の側つまり関連元のテーブルに外部キーがあります.
関連先は table per concrete class を使っているので,外部キーが指すのは複数のテーブルのいずれかの行です.
このため関連元のテーブルには,外部キーに加えてその外部キーが指すテーブルを識別するカラムが必要になります.
このカラムのことを... なんと呼ぶのかな? 名前が見あたらない... (;_;)
ともかく,カラムが必要です.カラムの型や値は自由に選べるようです.


次にマッピングについて.
関連元では,table per concrete class な永続クラスへの多対1の関連を

  • <any> 要素

を使ってマッピングします.
この要素は次の属性を持っています.

name
プロパティの名前を指定します.必須です.
id-type
外部キーの型を指定します.必須です.
meta-type
関連先のクラスを示すカラムの型を指定します.
省略すると Hibernate 型の class になります.
cascade
カスケード更新を指定する場合に指定します.
省略すると none になります.
access
Hibernate がプロパティにアクセスする方法を指定します.
省略すると property になります.

問題のカラムの型を meta-type 属性で指定します.
デフォルトの class を指定した場合は,問題のカラムの値は永続クラスの完全限定名となります.
その他の型を指定した場合は,その値と永続クラスの型への対応を <any> 要素の子要素である

  • <meta-value> 要素

で指定します.
この要素は,次の属性を持っています.

value
問題のカラムの値.必須です.
class
問題のカラムの値が value 属性の値と一致した場合の永続クラス.

さて,問題のカラムについて,型の指定方法は分かりましたが,カラム名はどうやって指定するのでしょうか? なんかよく分からない... (;_;)
うーみゅ,DTD を見ると,<any> 要素の内容モデルは次のようになっています.

<!ELEMENT any (meta*,meta-value*,column,column+)>

むむぅ,<column> 要素が一つあって,その後にまた1つ以上の <column> 要素が続く...
もしや,最初のカラムが問題のカラム,その後続くのが外部キーのカラムか!?
うー,そういわれてみれば「5.2.5. Any type mappings」の例でも最初のカラムの名前が table_name とかってそれっぽい例になってるなぁ.よし,そういうことにしましょう.


ということでお試しです.
table per concrete class な継承を使ったところはこれまで通り.
それへの多対1関連を持つクラスとしては... んー,困ったな.何にしようかなぁ?
いいや,CM でいきます.
...センスゼロ。
そのテーブルですが,タレント・モデル・女優は前回と同じです.
CMのテーブルは次の通り.

CREATE TABLE CM (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR,
    TALENT INTEGER,
    TALENT_TYPE VARCHAR
)

TALENT がタレントへの外部キー,TALENT_TYPE がタレントの型を示すカラムです.
次に永続クラスですが,タレント・モデル・女優については前回というか「継承その1」から変わっていません.
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 implements Lifecycle {
    int id = -1;
    String name;
    Talent talent;

    public Cm() {
    }
    public Cm(String name, Talent talent) {
        this.name = name;
        this.talent = talent;
    }
    public String toString() {
        return name + " : " + talent;
    }

    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;
    }
}

マッピングファイルもタレント・モデル・女優は前回と同じです.
そして 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"/>
        <any name="talent" id-type="integer" meta-type="string" access="field" cascade="save-update">
            <meta-value value="T" class="Talent"/>
            <meta-value value="M" class="Model"/>
            <meta-value value="A" class="Actress"/>
            <column name="talent_type"/>
            <column name="talent"/>
        </any>
    </class>
</hibernate-mapping>

このように,CM テーブルの TALENT_TYPE の値と実際の型を指定しています.
また,カスケード更新を使うようにしました.でも,対多関連では当てにならない削除はなしで♪
4つのマッピングファイルを全て,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();

            Model yuri = new Model("Yuri Ebihara", "CanCam");
            Actress akiko = new Actress("Akiko Yada", "My Little Chef");

            Cm aviva = new Cm("Aviva", yuri);
            session.save(aviva);
            Cm zespri = new Cm("Zespri", yuri);
            session.save(zespri);
            Cm aflac = new Cm("AFLAC", akiko);
            session.save(aflac);

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

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

ウッチーが CM にでてるのは知らない (民放のオリンピック共同 CM に出るとか聞いたような?) ので,エビちゃんとアッコちゃんだけにしてしまいました.残念!!!!
…イヤべつに残念でもなんでもないのですが、
こいつを実行!!!!

 onSave() : Aviva
 onSave() : Yuri Ebihara
 Hibernate: insert into Model (name, magazine, id) values (?, ?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: insert into Cm (name, talent_type, talent, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Zespri
 Hibernate: insert into Cm (name, talent_type, talent, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 onSave() : AFLAC
 onSave() : Akiko Yada
 Hibernate: insert into Actress (name, drama, id) values (?, ?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: insert into Cm (name, talent_type, talent, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: select cm0_.id as id, cm0_.name as name, cm0_.talent_type as talent_t3_, cm0_.talent as talent from Cm cm0_
 Hibernate: select model0_.id as id0_, model0_.name as name0_, model0_.magazine as magazine0_ from Model model0_ where model0_.id=?
 onLoad() : Yuri Ebihara
 onLoad() : Aviva
 onLoad() : Zespri
 Hibernate: select actress0_.id as id0_, actress0_.name as name0_, actress0_.drama as drama0_ from Actress actress0_ where actress0_.id=?
 onLoad() : Akiko Yada
 onLoad() : AFLAC
 Aviva : Yuri Ebihara : CanCam
 Zespri : Yuri Ebihara : CanCam
 AFLAC : Akiko Yada : My Little Chef

おおぉぉぉぉっ,でけたっぽい.最近本当に吐血が減ったなぁ〜.
実行後に CM のテーブルを確認すると,TALENT_TYPE カラムには 'M''A' がちゃんと入っていました.
ここで CM のマッピングファイルを次のように変更して...

        <any name="talent" id-type="integer" access="field" cascade="save-update">
            <column name="talent_type"/>
            <column name="talent"/>
        </any>

これで実行すると,TALENT_TYPE カラムにはモデルクラスや女優クラスの完全限定名が入りました.


ということで,<any> は終了です.次回はきっと <many-to-any> です.きっと来週です.
そして EJB 3.0 の方は今日もお休みです... 申し訳ないっ!
いや〜見捨てないでくださいね〜(*_*)