Hibernate 入門記 セッションその9 ライフサイクル

懸案の後始末も無事学習できたので,なんだかセッションはもういいよって気分の今日この頃.
そして,セッションの残りもあとわずかということで,駆け足で終わらせちゃいましょう.


ということで,まずは「9.8. Lifecyles and object graphs」です.lifecycles のスペルが怪しいのは原文のままです.(^^;
むむぅ,意外と長いな... っていうか文章ばかりだし.この前,自分はコードより文章を読んで理解する方が好みだと書いたのですが,英語だとそうもいきません.しくしくしく... しょうがねー,頑張って読むか.


えっとですね,永続オブジェクトは関連づけられた永続オブジェクトを持つ場合があります.関連づけられた永続オブジェクトもまた別の永続オブジェクトに関連づけられていたり.そんなわけで,永続オブジェクトのグラフが出来るわけです.
そんなグラフの中の複数の永続オブジェクトを変更するには,二つの方法があるとのこと.

  • save()update()saveOrUpdate() を個々のオブジェクトに対して呼び出す.
  • cascade="all" または cascade="save-update" を使った関連にマッピングする.

同様に,複数のオブジェクトを削除する場合も,

  • delete() を個々のオブジェクトに対して呼び出す.
  • cascade="all"cascade="all-delete-orphan"cascade="delete" のいずれかを使った関連にマッピングする.

むむぅ... cascade="all-delete-orphan" なんてあったんだぁ.知りませんでしたよ.残念!!!!


ともあれ,お題は「Hibernate おすすめ! カスケード属性」.

  • 親子関係にある永続オブジェクトにおいて,子オブジェクトのライフサイクルが親オブジェクトにピッタリ (ぺったり?) 同じになる場合は cascade="all" を指定しよう!
  • そうでなければ自分で save() delete() したまえ.
  • え? 面倒? しょうがないなぁ,それなら cascade="save-update" にして,delete() だけ自分でやりたまえ.

ってことらしいです.そうだよねぇ,特に多対1とか多対多の削除はカスケードではうまくいきそうもない感じですからね.いいんじゃないでしょうか.
そんでもって cascade="all" についてですが,これって親の save/update/delete が子の save/update/delete を引き起こすだけなので注意しろとのこと.ある子オブジェクトが親から参照されなくなっても,その子が勝手に削除される訳じゃないからだそうで.ふむふむ.
しかし例外が.それは1対多の関連で,cascade="all-delete-orphan" の場合.
それはちょっと置いておいて,カスケードされた操作の動きは次のようになるそうな.

  • 親が保存された場合,全ての子は saveOrUpdate() に渡される.
  • 親が update() または saveOrUpdate() に渡された場合,全ての子は saveOrUpdate() に渡される.
  • まだ永続化されていないオブジェクトが子として親に関連づけられた場合,その子は saveOrUpdate() に渡される.
  • 親が削除された場合,全ての子は delete() に渡される.

これに加えて,cascade="all-delete-orphan" の場合には,親から関連づけられなくなった子は削除されるみたい.orphan って「孤児」ですか.親がいなくなった子を削除するから all-delete-orphan なのですね.


そんなわけで,Hibernate は "persistence by reachability" を完全にサポートしているわけではないとのこと."persistence by reachability" は ODBMS や JDO なんかで採用されているもので,永続オブジェクトに関連づけられたものはみな永続オブジェクトだってやつです.個人的には "persistence by reachability" は好きじゃないので全然 OK.
でまぁ,cascade="save-update" でも十分便利だろ? ってことらしい.
そんでもって,なんだ? それを関連のデフォルトに出来るわけ? <hibernate-mapping> 要素で指定で着るみたい.ほほー.なるほど,マッピングファイルのルート要素である <hibernate-mapping> 要素には default-cascade なる属性がありますよ.でも TDT 的には all-delete-orpan は指定できないみたい.残念!!!!
ぐはぁっ,default-access なんて属性もあるよ! なんだ,いつも access="field" ってまじめに書いてたじゃないですか... 残念!!!!


さて,ライフサイクルはこんなところで終了.次は「9.9. Interceptors」です.
...
ごめんなさい,今日はもう無理.ライフサイクル文字多すぎ.読むの疲れた.
なので,お試ししちゃいます.
当然ながら1対多関連で cascade="all-delete-orphan" です.
ということでテーブル.久しぶりに雑誌登場.

CREATE TABLE MAGAZINE (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR
)

CREATE TABLE MODEL (
    ID INTEGER IDENTITY PRIMARY KEY,
    NAME VARCHAR,
    MAGAZINE VARCHAR
)

雑誌とモデルの関係は1対多です.
ということで雑誌の永続クラス.以前のと変わってませんが一応.

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 Magazine implements Lifecycle {
    int id = -1;
    String name;
    Set model;

    public Magazine() {
    }
    public Magazine(String name) {
        this.name = name;
        this.model = new HashSet();
    }
    public String toString() {
        return name + model;
    }

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

雑誌のマッピングファイル.

<?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" default-access="field">
    <class name="Magazine">
        <id name="id" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name"/>
        <set name="model" cascade="all-delete-orphan">
            <key column="magazine"/>
            <one-to-many class="Model"/>
        </set>
    </class>
</hibernate-mapping>

モデルとの関連に cascade="all-delete-orphan" を指定しています.
ついでに default-access="field" も指定しました.へへっ(えみちい風).
モデルの永続クラス.

package study;
import java.io.Serializable;
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;

    public Model() {
    }
    public Model(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;
    }
}

モデルのマッピングファイル.

<?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" default-access="field">
    <class name="Model">
        <id name="id" unsaved-value="-1">
            <generator class="identity"/>
        </id>
        <property name="name"/>
    </class>
</hibernate-mapping>

雑誌とモデルのマッピングファイルを hibernate.cfg.xml に追加します.
そして実行用のクラス.

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

public class Main {
    public static void main(String[] args) {
        try {
            HibernateTemplate template = new HibernateTemplate();

            System.out.println("*** create magazine ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    session.save(new Magazine("CanCam"));
                    session.save(new Magazine("ViVi"));
                    return null;
                }
            });

            System.out.println("*** create model ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    Magazine cancam = (Magazine) session.get(Magazine.class,
                            new Integer(0));
                    cancam.model.add(new Model("Yuri Ebihara"));
                    cancam.model.add(new Model("Asami Usuda"));

                    Magazine vivi = (Magazine) session.get(Magazine.class,
                            new Integer(1));
                    cancam.model.add(new Model("Sayo Aizawa"));
                    cancam.model.add(new Model("Jun Hasegawa"));
                    return null;
                }
            });

            System.out.println("*** print ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    Iterator it = session.find("from study.Model").iterator();
                    while (it.hasNext()) {
                        System.out.println(it.next());
                    }
                    return null;
                }
            });

            System.out.println("*** clear association ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    Iterator it = session.find("from study.Magazine magazin")
                            .iterator();
                    while (it.hasNext()) {
                        ((Magazine) it.next()).model.clear();
                    }
                    return null;
                }
            });

            System.out.println("*** print ***");
            template.process(new HibernateCallback() {
                public Object doProcess(Session session) throws Exception {
                    Iterator it = session.find("from study.Model").iterator();
                    while (it.hasNext()) {
                        System.out.println(it.next());
                    }
                    return null;
                }
            });
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

ちょっと細かいですが,最初のセッションでは雑誌だけ作って永続化しています.
次のセッションではそれにモデルを関連づけていますが,明示的に save() はしていません.雑誌の取得で ID 決めうちなのは思いっきり手抜きです.心より恥じる.
そして4つめのセッションで雑誌とモデルの関連を取り除いています.でもなにも delete() はしていません.
こいつを実行!!!!!!

*** create magazine ***
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()
*** create model ***
Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ 
           from Magazine magazine0_ where magazine0_.id=?
onLoad() : CanCam
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
           model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
Hibernate: select magazine0_.id as id0_, magazine0_.name as name0_ 
           from Magazine magazine0_ where magazine0_.id=?
onLoad() : ViVi
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
           model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
onSave() : Jun Hasegawa
Hibernate: insert into Model (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: update Model set magazine=? where id=?
Hibernate: update Model set magazine=? where id=?
Hibernate: update Model set magazine=? where id=?
Hibernate: update Model set magazine=? where id=?
*** print ***
Hibernate: select model0_.id as id, model0_.name as name 
           from Model model0_
onLoad() : Jun Hasegawa
onLoad() : Yuri Ebihara
onLoad() : Asami Usuda
onLoad() : Sayo Aizawa
Jun Hasegawa
Yuri Ebihara
Asami Usuda
Sayo Aizawa
*** clear association ***
Hibernate: select magazine0_.id as id, magazine0_.name as name 
           from Magazine magazine0_
onLoad() : CanCam
onLoad() : ViVi
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
           model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
           model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
onLoad() : Jun Hasegawa
onLoad() : Yuri Ebihara
onLoad() : Asami Usuda
onLoad() : Sayo Aizawa
onDelete() : Sayo Aizawa
onDelete() : Asami Usuda
onDelete() : Jun Hasegawa
onDelete() : Yuri Ebihara
Hibernate: update Model set magazine=null where magazine=?
Hibernate: delete from Model where id=?
Hibernate: delete from Model where id=?
Hibernate: delete from Model where id=?
Hibernate: delete from Model where id=?
*** print ***
Hibernate: select model0_.id as id, model0_.name as name from Model model0_

ふむふむ.
2 つめのセッション (create model) で,永続化されている雑誌に新しいモデルを関連づけただけで,そのモデルがちゃんと永続化されました.
そして 4 つめのセッション (clear association) で,関連を取り除いただけでモデルが削除されてしまいました.
大・成・功.
ちなみに,雑誌のマッピングファイルで cascade="all" にすると次のようになります (clear association 以降のみ).

*** clear association ***
Hibernate: select magazine0_.id as id, magazine0_.name as name 
           from Magazine magazine0_
onLoad() : CanCam
onLoad() : ViVi
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
           model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
Hibernate: select model0_.magazine as magazine__, model0_.id as id__, 
           model0_.id as id0_, model0_.name as name0_ 
           from Model model0_ where model0_.magazine=?
onLoad() : Jun Hasegawa
onLoad() : Yuri Ebihara
onLoad() : Asami Usuda
onLoad() : Sayo Aizawa
Hibernate: update Model set magazine=null where magazine=?
*** print ***
Hibernate: select model0_.id as id, model0_.name as name from Model model0_
onLoad() : Jun Hasegawa
onLoad() : Yuri Ebihara
onLoad() : Asami Usuda
onLoad() : Sayo Aizawa
Jun Hasegawa
Yuri Ebihara
Asami Usuda
Sayo Aizawa

こっちの場合,関連を取り除いただけではモデルは削除されていないことが分かります.


ということで,カスケードについてもだいたい理解できました.
さて,明日でセッション終わりに出来るかなぁ? 終わりにしたいなぁ.頑張るのだぁ.