「めざましテレビ」

今日の「早耳トレンドNo1」は臼田あさ美ちゃーん,お題は「秋を先取り!! 最新スニーカー」.
スニーカーかぁ.一応ファッションねたですよね.でも微妙にうれしくない...
なんか,足元ばっかり映ってるせいでしょうか.いやその,脚が見れるのならうれしんですが,微妙に違う... もうちょっと上... 残念!!!!
それにしてもあさ美ちゃん一人勝ち (?) 状態ですね.先週に続いて今週も三日連続あるかも?
っていうかあさ美ちゃん夏休みあるのかなぁ? 休んでないよね?
大変だと思いますが,頑張れ,頑張れ,頑張れぇ〜 (えみちい風)

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

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


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

S2Axis 開発記 準備その3 Axis のアーキテクチャ & JWSHandler 再訪

昨日書いたように,JWS の実装を参考に,リクエストに応じて S2 コンテナから取得したコンポーネントWebサービスとして公開するという方向で考えています.
この方法だと,特別な手続きなしでコンポーネントを Web サービス化できるわけですが,一方でセキュリティホールになりかねないという懸念も出てきそうです.なので,やっぱり何らかの方法で公開するコンポーネントを制限できるように考えないとダメかも.


それはさておき,JWS の実装を学習したいわけですが,その前に Axis そのものの学習を.
Axis の実装に関しては,「Axis Architecture Guide」に詳しい解説があります.
それによると,Axis というのは「Message を処理するだけのもの」だそうです.
Message にはリクエストメッセージとレスポンスメッセージがあります.サーバ側だと,リクエストメッセージを受け取って,レスポンスメッセージを返すわけですね.
リクエストメッセージやレスポンスメッセージ,それに様々な情報の入れ物が MessageContext です.
そして「Message を処理するもの」が Handler です.さらに,Handler の集合が Chain.
Handler/Chain は,リクエストメッセージを処理するものとレスポンスメッセージを処理するものに分けられるようです.
Axis というのは,多数の Handler が次々と呼び出されて,リクエストメッセージが解釈されて,レスポンスメッセージが生成されて... というものみたいです.


Handler/Chain は,3つのレイヤに分けられるようです.

  • Transport
  • Global
  • Service

それぞれがどういうものか,詳しくは説明されていないのですが,何となく名前で想像が付く... かな?
下っ端の Service はまさに特定のサービスを呼び出すためのもの.Global は多数のサービスに共通の処理を行うためのもの.たぶん.
Transport も Global も,リクエストおよびレスポンスの Chain を持ちます.
Service も同じようにリクエストおよびレスポンスの Chain を持ちますが,さらに Provider と呼ばれるものも持ちます.Provider もまた Handler ということですが,通常はこれが本当にサービスを提供するオブジェクトを呼び出す役目を持っているようです.
ということで,まとめると Handler/Chain は次の順番で呼び出されるということになります (サーバ側).

  • Transport Request Chain
    • Global Request Chain
      • Service Request Chain
      • Service Provider
      • Service Response Chain
    • Global Response Chain
  • Transport Response Chain

ふぅ.


さて,ここで JWSHandler なんですが,こいつはおそらく Global のリクエストを処理する Chain に組み込まれる Handler と思われます.ですから,特定のサービスを処理する Chain に先だってメッセージを処理することが出来るわけです.たぶん.
JWSHandler はまず,MessageContext からリクエストの「パス」を調べます.いわゆる URI ですね.そして,それが拡張子 .jws だったら (拡張子は Axis の設定で変更可能みたいです) ,該当の JWS ファイルを探し,コンパイルします (もちろんキャッシュしますがそれはひとまず横へ).コンパイルされたクラスはサービスを提供する実体 (Target) です.
そして JWSHandler は,その Target を呼び出すための Service を作成します.それは SOAPService というクラスです.こいつは Service なので Provider を持ちます.それが RPCProvider です.
この SOAPServiceMessageContext に設定すると,JWSHandler の後に SOAPService のリクエスト Handler が,そして RPCService が呼び出されます.RPCService は Target のサービス (JWS ファイルがコンパイルされたもの) を呼び出します.RPCService はその結果からレスポンスメッセージを作成し,MessageContext に設定するのでしょう.そしてレスポンス Handler が呼び出され...
と,こんな感じで動いているようです.


なんとなく,S2Axis として何をすればいいかが見えてきますね.
まずは Global な Handler を作らなくてはなりません.これを仮に S2Handler とでもしましょうか.
S2Handler は,リクエストのパスを調べて,それが S2 コンポーネントの呼び出しであれば SOAPService を作成し,RPCProvider を設定します.この RPCService が呼び出すのは S2 コンテナから取得した DI 済みのコンポーネントです.このあたりは,S2Struts なんかがやっていることとそっくりかも?
...
といったところでちょっと課題.
JWSHandler の場合,RPCProvider にはクラスを設定しています.そのインスタンスを作成するのは RPCService の役割みたいです.しかし S2Handler の場合はインスタンスを設定しなければなりません.出来るのでしょうか? 要調査.
それから,S2Handler を作ったとして,どうやって組み込むのか知らなかったり.残念!!!!
後はなんだろう? あまり課題はないかも?


課題じゃないけど決めなきゃいけないことはありますね.
例えばパス (URI) から S2 のコンポーネントマッピングするルールとか.
どんな制約があるのかとか調べていませんが,安易に考えるなら
ContextRoot/S2AxisRoot/NameSpace/ComponentName
みたいな感じでしょうか.S2AxisRoot は Axis の設定で変更可能に出来るはず.JWS の拡張子のように.デフォルトは s2axis とか.
そうだ,同じく Axis の設定で,Web サービスとして公開するコンポーネント名前空間を制限できるというのはどうでしょう? 例えば名前空間 wsコンポーネントだけ公開するとしておくと,それ以外のコンポーネントにアクセスしようとしても無駄無駄無駄無駄無駄無駄無駄無駄無駄ぁぁぁぁぁ.みたいな.
それとも,コンポーネントを公開する/しないは dicon ファイルで設定できた方がいいでしょうか?
ご意見あればぜひコメントください.m(__)m