Hibernate入門記 マッピング定義その7 component & dynamic-component 要素

またしてもたっぷりと間が空いてしまいました.心より恥じる.コンスタントに継続するって本当に難しいですね.日記じゃなかったら投げ出しちゃってますよ.日記でも投げ出し気味ですが.無念だ.
今回は「5.1.12. component, dynamic-component」へと進みます.実はこれ,「7. Component Mapping」で詳しく解説されているようなので,ここまでの慣習に従えばスキップなのですが,簡単そうだし,久々でなまった頭にはちょうどいい感じなのでやってしまいましょう.
なんでも,コンポーネントというのはテーブルのカラムを永続オブジェクトの子オブジェクトとしてマップするようなものらしいです.住所とか電話番号とかに使うと便利そうですね.っていうかid:fumi0611さんが住所でやってましたっけ.
ということで,そのコンポーネントを定義するのが,

  • <component> 要素

です.
これは class 要素の子として記述することができます.
そしてその属性は次の通り.

name
このコンポーネントを参照する(親の)プロパティ名を指定します.
class
このコンポーネントのクラス名を指定します.
insert/update
INSERT/UPDATEのSQL文にこのプロパティを含めない場合に false を指定します.デフォルトは true です.
access
Hibernate がこのプロパティにアクセスする方法を指定します.

それから,子要素として <property> 要素や <many-to-one> 要素などを記述できます.
また,<parent> という子要素を指定することで,親となる永続オブジェクトへの参照を持つことが出来るようです.これはテーブルに対応するカラムを持つものではないと思われます.


さらにもうひとつ,

  • <dynamic-component> 要素

というのも解説されています.
これは,テーブルのカラムを(任意のクラスではなく) Map にマップするらしいです.Map のキーは <property> 要素等で指定されたプロパティ名で,値は対応するカラムの値です.ふむ.
ozacc さんの翻訳(2.0.x)だと,Commons の beanutils のことが書いてあるのですが,2.1.xの方には書いてありません.変更されたのでしょうか?
なんにせよ,<component> 要素に比べるとあまり使わないような気がします.
使い方はおおむね <component> 要素と同様.class 属性の指定ができない点だけが違うみたいです.


といったところでさっそくお試ししましょう.
例によってネタ不足なので,今回は姓名をコンポーネントとして扱ってみます.
それ以外は興味がないので,関連も使わずいたってシンプルなテーブルを用意.

CREATE TABLE MODEL (
    ID INTEGER IDENTITY,
    FIRSTNAME VARCHAR,
    LASTNAME VARCHAR,
    MAGAZINE VARCHAR
)

まずは姓名を表す Name クラス.

package study;

public class Name {
    String firstName;
    String lastName;

    public Name() {
    }
    public Name(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    public String toString() {
        return firstName + " " + lastName;
    }
}

うーん,equals(Object) とか hashCode() はオーバーライドしなくていいのかな? ここには書いていないので,このまま進んじゃいましょう.
お次はこれを使う Model クラス.雑誌は Map として扱っちゃいましょう.カラム一個だけなので全然意味はありませんが,今は使うことに意義があるのです.

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 Model implements Lifecycle {
    int id = -1;
    Name name;
    Map map;

    public Model() {
    }
    public Model(String firstName, String lastName) {
        this.name = new Name(firstName, lastName);
        this.map = new HashMap();
    }
    public String toString() {
        return name + ", " + map;
    }

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

        <component name="name" class="study.Name" access="field">
            <property name="firstName" access="field"/>
            <property name="lastName" access="field"/>
        </component>
        <dynamic-component name="map" access="field">
            <property name="magazine"/>
        </dynamic-component>
    </class>
</hibernate-mapping>

最後に実行用のクラス.

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");
            yuri.map.put("magazine", "CanCam");
            session.save(yuri);

            Model sayo = new Model("Sayo", "Aizawa");
            sayo.map.put("magazine", "ViVi");
            session.save(sayo);

            Model naoko = new Model("Naoko", "Tokuzawa");
            naoko.map.put("magazine", "CanCam");
            session.save(naoko);

            session.flush();

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

CanCam勢が二人になってしまいました.Rayから一人連れてこようかと思ったのですが,香里奈とか彩友美とか,なぜだか姓のない子が目につくのでやめました.
そんなことはどうでもよくて,実行!!

 net.sf.hibernate.MappingException: Could not determine a property type for: magazine
     at net.sf.hibernate.cfg.Binder.bindProperty(Binder.java:444)
     at net.sf.hibernate.cfg.Binder.createProperty(Binder.java:1070)
     at net.sf.hibernate.cfg.Binder.bindComponent(Binder.java:858)
     at net.sf.hibernate.cfg.Binder.propertiesFromXML(Binder.java:1041)
     at net.sf.hibernate.cfg.Binder.bindRootClass(Binder.java:361)
     at net.sf.hibernate.cfg.Binder.bindRoot(Binder.java:1243)
 ・
 ・
 ・

ぐはぁっ,またやってもーた.心より恥じる.
うーみゅ,magazine の型がわからない? そうか,永続クラス上では Map になっているからリフレクションで型を求めることができないということですか.データベースのメタデータから取ってくるとかしないのでしょうか? しないからこうなるのですね.無念だ.
ということで,マッピングファイルの magazine のところに型情報を追加!

            

そして実行!!!!

 onSave() : Yuri Ebihara, {magazine=CanCam}
 Hibernate: insert into Model (firstName, lastName, magazine, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Sayo Aizawa, {magazine=ViVi}
 Hibernate: insert into Model (firstName, lastName, magazine, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 onSave() : Naoko Tokuzawa, {magazine=CanCam}
 Hibernate: insert into Model (firstName, lastName, magazine, id) values (?, ?, ?, null)
 Hibernate: CALL IDENTITY()
 Hibernate: select model0_.id as id, model0_.firstName as firstName, model0_.lastName as lastName, model0_.magazine as magazine from Model model0_
 onLoad() : Yuri Ebihara, {magazine=CanCam}
 onLoad() : Sayo Aizawa, {magazine=ViVi}
 onLoad() : Naoko Tokuzawa, {magazine=CanCam}
 Yuri Ebihara, {magazine=CanCam}
 Sayo Aizawa, {magazine=ViVi}
 Naoko Tokuzawa, {magazine=CanCam}

おっけー♪


<component> は結構使えるかも.ちょっとした値オブジェクトを豊富に用意すると便利だと思います.
ん? コンポーネントって値オブジェクトだよねぇ.ってことはやっぱり equals(Object)hashCode() はオーバーライドすべきですね... 心より恥じる.