Hibernate 入門記 セッションその7 削除とフラッシュ

緊急連載「Axis 入門記」に切り替えようかとも思ったのですが,やっぱりこっちを淡々と進めようということで,今回は「9.5. Deleting persistent objects」です.
永続オブジェクトをデータベースから削除するには,Session

    • void delete(Object object)

を使用します.
この削除はあくまでもデータベースからの削除なので,delete(Object) の引数に渡したオブジェクトはその後も使うことが出来ます.ただし,それはもはや永続化されたオブジェクトではなくなります.
多数の永続オブジェクトをまとめて削除したい場合もあります.そんな場合には問い合わせ文字列を指定して削除することも出来ます.それが Session

    • int delete(String query)
    • int delete(String query, Object value, Type type)
    • int delete(String query, Object[] values, Type[] types)

です.
問い合わせ文字列にはパラメータを含めることが出来ます.戻り値は削除した永続オブジェクトの数です.
永続オブジェクトを削除する際,関連の外部キーに関しては Hibernate が面倒を見てくれるようです.外部キー制約違反になることはないみたい.でも,NOT NULL 制約違反には気をつけろ.らしい.
削除については以上.ラクチンっぽい.


ということでお試しコーナー.
と思ったのですがちょっと中身なさすぎなので,「9.6. Flush」へ進みます.
セッションは,保持している永続オブジェクトの状態と RDB との同期を取るために,SQL を実行する場合があります.それがフラッシュです.
いつフラッシュが行われるかというと,次の場合らしいです.

  • Session#find()Session#iterate() が呼び出された場合.
  • Transaction#commit() が呼び出された場合.
  • Session#flush() が呼び出された場合.

ぐはぁっ,トランザクションをコミットすれば勝手にフラッシュされたのか... これまでのお試しコーナーではコミットとフラッシュを連チャンで呼び出してますがな.心より恥じる (Zaizener 再び!).
んでその SQL ですが,次の順序で発行されるとのこと.

  1. Session#save() が呼び出されたのと同じ順序で INSERT.
  2. エンティティの UPDATE.
  3. コレクション (関連) の DELETE.
  4. コレクションの要素の DELETE,UPDATE,INSERT.
  5. コレクション (関連) のINSERT.
  6. Session#delete() が呼び出されたのと同じ順序で DELETE.

このように SQL が発行されることは保証されているとのことですが,それがいつ行われるかは Session#flush() を明示的に呼び出した場合を除いて保証されないとのことです.
ですが,Session#find() などが古い状態のオブジェクトを返すことはないのだとか.
うーみゅ,Session#find() するとフラッシュされると書いてあったわけですが... つまりあれですかね,Session#find() などが対象とするエンティティについては間違いなくその時点までにフラッシュするけど,関係のないエンティティについてはフラッシュされているとは限らないということかなぁ? いいや,後でお試ししましょう.

2004/08/18 03:15 追記
ぐはぁっ,おためし忘れてました...
心より恥じる.

このようなセッションの振る舞いは変更できるようです.それには,Session

    • void setFlushMode(FlushMode flushMode)

です.
FlushMode には,次の3種類が用意されています.

NEVER
セッションは明示的に Session#flush() しないかぎりフラッシュされません.
リード・オンリーなトランザクションで効果的とのことです.
COMMIT
Session#commit() が呼び出されたときにもフラッシュされます.
AUTO
問い合わせが古い状態を返すことのないように,問い合わせの前にもフラッシュされます.
これがデフォルトのモードです.

なるほど.


ということで,今度こそお試しコーナーです.
テーブル定義,永続クラス,マッピングファイルは前回と同じ.手抜きじゃありませんよぉ〜.
なので,今回新たに用意するのは実行用のクラスのみ.

package study;
import java.util.Iterator;
import net.sf.hibernate.FlushMode;
import net.sf.hibernate.LockMode;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
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();

            System.out.println("starting session (NEVER)");
            Session session = factory.openSession();
            session.setFlushMode(FlushMode.NEVER);
            Transaction tx = session.beginTransaction();

            Model model = new Model("Yuri Ebihara", "CanCam");
            session.save(model);
            model.name = "Asami Usuda";

            System.out.println("*** NEVER (before commit) ***");
            Iterator it = session.find("from study.Model").iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            tx.commit();

            System.out.println("*** NEVER (after commit) ***");
            it = session.find("from study.Model").iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            session.flush();

            System.out.println("*** NEVER (after flush) ***");
            it = session.find("from study.Model").iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            tx.commit();

            System.out.println("\nstarting session (COMMIT)");
            session = factory.openSession();
            session.setFlushMode(FlushMode.COMMIT);
            tx = session.beginTransaction();

            model.name = "Sayo Aizawa";
            model.magazine = "ViVi";
            session.update(model);

            System.out.println("*** COMMIT (before commit) ***");
            it = session.find("from study.Model").iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            tx.commit();

            System.out.println("*** COMMIT (after commit) ***");
            it = session.find("from study.Model").iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            System.out.println("\nstarting session (AUTO)");
            session = factory.openSession();
            session.setFlushMode(FlushMode.AUTO);
            tx = session.beginTransaction();

            session.lock(model, LockMode.READ);
            session.delete(model);

            System.out.println("*** AUTO (before commit) ***");
            it = session.find("from study.Model").iterator();
            while (it.hasNext()) {
                System.out.println(it.next());
            }

            tx.commit();
        }
        catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

今回はセッションを 3 回作成しています.それぞれの FlushModeNEVERCOMMITAUTO に設定しています.
こいつを実行!!!!

starting session (NEVER)
onSave() : Yuri Ebihara
Hibernate: insert into Model (name, magazine, id) values (?, ?, null)
Hibernate: call identity()
*** NEVER (before commit) ***
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_
Asami Usuda (CanCam)
*** NEVER (after commit) ***
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_
Asami Usuda (CanCam)
Hibernate: update Model set name=?, magazine=? where id=?
*** NEVER (after flush) ***
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_
Asami Usuda (CanCam)

starting session (COMMIT)
onUpdate() : Sayo Aizawa
*** COMMIT (before commit) ***
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_
Sayo Aizawa (ViVi)
Hibernate: update Model set name=?, magazine=? where id=?
*** COMMIT (after commit) ***
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_
Sayo Aizawa (ViVi)

starting session (AUTO)
Hibernate: select id from Model where id =?
onDelete() : Sayo Aizawa
*** AUTO (before commit) ***
Hibernate: delete from Model where id=?
Hibernate: select model0_.id as id, model0_.name as name, model0_.magazine as magazine 
           from Model model0_

むむぅ? ちょっと予定外の結果だ...
まずは NEVER のセッションですが,友里ちゃんで作った永続オブジェクトをあさ美ちゃんに更新しているのですが,実際に UPDATE されているのはコミットとフラッシュの間です.これは意図したとおり.
しかし,コミット前もコミット後も,問い合わせ結果の表示は友里ちゃんではなくてあさ美ちゃんです.なぜ??
そうかぁ,問い合わせ後に onLoad() コールバックの表示がありませんね.つまり,メモリ上にある永続オブジェクトがそのまま使われるので更新後の内容なんですね.やられた...
次の COMMIT のセッションですが,こちらではモデルを紗世ちゃんに更新しています.その UPDATE はちゃんとコミットの前に実行されています.
最後の AUTO のセッションでは,前回お試しを忘れていた Session#lock() を使っています.LockMode#READ を指定しているため,その時点で ID を確認する SELECT が実行されています.このセッションでは,その永続オブジェクトを削除しています.その DELETE は,コミットもフラッシュもしていませんが,問い合わせの直前に実行されています.そのため,問い合わせ結果には何も表示されていません.


ということで,問い合わせ結果の表示は必ずしも DB の状態と一致しないことに注意という,かなり基本的なところでびびってしまいましたが,永続オブジェクトの削除とフラッシュモードについてはちゃんと理解できたようです.ついでに前回お試しを忘れていた Session#lock() も試すことが出来ました.よしよし.
...
実は一つ,白状しなければならないことがあります.
今回,「ぐはぁっ」していませんが,実は裏でこっそし吐血しまくりました.無念だ (Zaizener 再び!)
というのも,最初トランザクションのコミットには,従来通り

        session.connection().commit();

とやっていたのです.これでも結果的には Transaction#commit() が呼ばれるだろうと勝手に思いこんでいたのです.しかし... 呼ばれないようです.(ToT)
そのため,フラッシュモードが COMMIT なのに,コミット後も SQL が発行されなくて悩みました.心より恥じる. (Zaizener 再び!)


ということで,次回は遂に「9.7. Ending a Session」です.ようやく,よぉーやっく,セッションの後始末を学習できます.残念!!!!
…イヤべつに残念でもなんでもないのですが