« NetBeans7上でJDK7を用いてEclipseと結果が異なっていたコンパイラの挙動を確認する | トップページ | JOINが遅すぎて泣ける ― MySQLは漢(オトコ)のコンピュータ道? »

2011/05/08

Hibernateを使用した ― O/Rマッピングは漢(オトコ)の浪漫

 

サブタイトルには今のところ意味は無いです。

Hibbernate イン アクション(及びリバイズドエディションの Java Persistence with Hibernate)は、私が人からお勧めの本を聞かれた際の回答としてよく登場するもののうちの1冊です(ちなみに~インアクションの方はすでに廃版らしく、また、Java Persistence~の方は訳書が出ていません…)。今回のQconでGavin Kingさんが来日されると聞いて、これはサインを貰わねば、と2冊スタンバイしていたのですが取りやめになってしまって非常に残念でした。

そんな行動とは裏腹に、実のところ私はHibernateをガッツリ使ったことがなく、それどころかSQL文も SELECT * FROM ○ WHERE △ = ×; 以外の書式は入門書を見ながらでないと入力できないくらいDBに縁がありませんでした。

しかし今回、遂に、Hibernateに触れる機会がありましたので、実際に使用してみて詰まったところを記載しておこうと思います。

 

ちゃんと等価性を考慮したのにうまく動かない

先日のエントリでも触れた等価性の話です。Hibernateでは(その他JPA実装プロダクトでも)、

  • @Idアノテーションをつけたフィールドは人工キーにし、RDB上でprimary key制約をつける
  • 人工キーとは別に自然キーを持たせ、RDB上ではunique制約をつける
  • 等価性、つまりequalsメソッドは自然キーを用いて実装する

というのが典型的なエンティティの実装かと思います。しかしこのように適切にequalsを実装し、これに沿う形でhashCodeを実装したとしても、@OneToManyでEager Fetchを行うと期待通り動作してくれません。これが以下のバグです。

実際の動作はコードが添付されているのでそちらを見てもらうことにして、ポイントは以下になります。

   1: @Entity(name = "Container")
   2: private static class Container
   3: {
   4: // 中略
   5:     @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, mappedBy = "container")
   6:     Set<Item> items = new HashSet<Item>();
   7: }

ここで”fetch = FetchType.EAGER”を取り除いてやれば(@OneToManyのデフォルトはLazyなので)期待通りの動作になります。

プライオリティはMajarとして登録されていますが、Javaの常識/ORMの常識が通用しないという意味で精神的にはクリティカルなバグでした…(わざわざWikipediaの短い説明文の中にも注釈うってるくらい重要なのに!)

「まあバグだったら仕方が無い、Lazy Fetchにするか」と思っても、次のバグが追い打ちをかけます。

これは、あるエンティティのプロパティ(フィールド)をクエリ条件の対象にしようとしてエイリアスを設定すると、エイリアスが設定されたエンティティは強制的にJOINされてくっついて取れてきちゃう、つまり強制的にEarger Fetchされてしまうので、合わせ技で回避方法が無くなります…

ちなみにHHH-3538(の重複元として登録されているHHH-2049)は、このエントリを書いている当日リリースされたバージョン3.6.4で修正されたようです。このバージョンを使えるわけではないので、これはこれで悔しいです…

[追記]

…と書いた矢先、HHH-3538とHHH-2049のDuplicateが取り消されて、このバグはReopenedになりました。実際は重複ではなくこちらはまだ未解消だったということのようですね。

 

DELETE –> INSERT したいのに INSERT –> DELETE になってしまう

理由は様々のようですが、UPDATEではなくDELETEしてからINSERTしたい、というような要望を時々耳にします(心の中ではDELETE-INSERTアンチパターンと呼んでいます。前述の通りRDBの実装にあまり関わったことが無いので本当にアンチパターンなのかどうかは分からないのですが)。で、そのDELETE-INSERTアンチパターンをHibernateで実現しようとしても、HibernateはINSERT優先なのでDELETEする前にINSERTが実行されprimary key制約違反になります。

こういう事象が、DELETE-INSERTアンチパターンを明示的に適用しなくても、通常の操作で発生してしまう、というバグです。

親子関係にあるようなエンティティが@OneToManyであり、それを@JoinTable(デフォルト戦略です)で実現しているときに子の挿げ替えを行うとunique制約違反になる、というものです。join tableはparent_idとchild_idというようなカラムを持っており、JPAの機能で自動的にテーブルを生成するとchild_idにunique制約が張られます。そしてHibernateは先に述べたようにINSERT優先、つまり新しい関連を先にRDBに反映させようとするのでchild_idが重複します。

unique制約を外したり、本当は@OneToManyなんだけど@ManyToManyに変えてしまうことで回避できると思うのですが、純潔派の方にとっては気持ちが悪いでしょう。

重複元HHH-1268のタイトルを見ると、削除だけでも発生するようですね…

[追記: 書籍Hibernate辞典を読み直していたら、「07-04-02 一対多関連の洗い替えを行いたい」という節で、上で「挿げ替え」と呼んでいる処理を「洗い替え」という名前で紹介していますね。検索してみると、そこそこ通用しそうな呼び名のように思われます。]

 

Listで@OneToManyを実現するとINSERTが2回走ってしまう

トランザクション内では特定の順番に並んでいてほしいけれど、トランザクションを抜けて再度ロードしたときには順番はどうでもいい、みたいなことを実現したいことがありまして、Listがまさにそうじゃないか、とやってみたところ見事にかかりました。(Hibernateネイティブの機能でListを使う場合には順序を保持して永続化することもできますが、JPA経由の場合はSetと同じく順序は保持されません。)

報告日も比較的最近、プライオリティもCriticalと、結構大物釣り上げたかな?と見つけた当初は思っていたのですが、実はあんまり記憶にありません…まあそんなことやろうと思うのは稀ですしね。

 

と、HibernateってORMの雄であり、結構枯れてるプロダクトかと思いきや、これが中々血気盛んです、というお話でした。他にも小さなバグには出会ったのですが、さすがに私が少し扱って見つけた範囲ではすべて報告済みのものばかりでした。

 

最後に。バグではないのですが。

LazyInitializationExceptionの怪

HibernateについてWebで検索していると、結構LazyInitializationException(LIE)で困ったというような報告がなされています。実際に使用してみるまではこれが結構疑問で、「駄目な使い方ってすぐわかりそうなもんなのになあ」なんて考えていました(マルチスレッドが絡む場合は別として)。

しかし他の問題が絡むと問題が厄介になることに気付きました。

トランザクション開始 –> インスタンスAのメソッド実行(処理1) –> インスタンスBのメソッド呼び出し(処理2) –> インスタンスAに戻ってくる(処理3) –> トランザクション終了

というような流れの中で、処理1でEntityManagerからエンティティをもらって、処理2でいくつかの計算を行った後、処理3でエンティティの関連先を参照しようとするとなぜかLIEが。しかも発生したりしなかったり。

Bを調べてみると、Session(JPAを使っていますので正確にはEntityManager)でクエリを実行した際に発生した例外を握りつぶしていることがわかりました。

Hibernateドキュメントの13.2.3.節「例外ハンドリング」にはこのように書かれています。

Session が例外 (SQLExceptionを含む) を投げた場合、直ちに、データベーストランザクションをロールバックし、Session.close() を呼び、 Session インスタンスを破棄すべきです。 Session のいくつかのメソッドは、セッションの状態を 矛盾したまま にします。 Hibernate が投げた例外を、回復できるものとして扱うことはできません。 finally ブロックの中で close() を呼んで、 Session を確実に閉じてください。

なので、インスタンスB自身は例外を握りつぶしても問題なかったからそのような実装になっていたのでしょうが、インスタンスAは自分のあずかり知らぬところで関連先が参照できないような状況になっていた…ということが起こりえます。

また、LIEが発生したりしなかったりする原因は、処理2の中でループで関連先を参照し、ある条件にヒットしたらbreakする、というような処理を行っていました。参照する先はHashSetで持っているので、処理2によってfetchされる関連先は実行の都度変わります。このため、たまたま処理3が参照する関連先エンティティを処理2によって事前にfetchしているような場合であればLIEが発生しない、という事象でした。

実行する度にエラーの有無が変わる、というのはC言語時代のバッファオーバランを彷彿とさせますね。しかもこのLIEの問題を調査しようとしてデバッグプリントを追加したりデバッガでステップ実行しながら関連先エンティティを調べたりしているとその処理によってfetchが行われ現象が再現しなくなる、問題の原因となるコード記述箇所と問題が発生する箇所が異なる、といようなことも然りです。

« NetBeans7上でJDK7を用いてEclipseと結果が異なっていたコンパイラの挙動を確認する | トップページ | JOINが遅すぎて泣ける ― MySQLは漢(オトコ)のコンピュータ道? »

コメント

この記事へのコメントは終了しました。

トラックバック


この記事へのトラックバック一覧です: Hibernateを使用した ― O/Rマッピングは漢(オトコ)の浪漫:

« NetBeans7上でJDK7を用いてEclipseと結果が異なっていたコンパイラの挙動を確認する | トップページ | JOINが遅すぎて泣ける ― MySQLは漢(オトコ)のコンピュータ道? »

other sites

  • follow us in feedly
  • github
  • stackoverflow

ソフトウェアエンジニアとして影響を受けた書籍

  • Christain Bauer: HIBERNATE イン アクション

    Christain Bauer: HIBERNATE イン アクション
    理論と実践が双方とも素晴らしい製品であるHibernate。本書はそのプロダクトを書名に冠していますが、Hibernateを使うつもりがなく、ORマッピングの解説書として読むにしても十分な良書です。Second EditionとしてJava Persistence With Hibernateという書籍も出版されていますが、残念ながら現在のところ 和訳はされていません。-インアクションは2.xの、Java Persistence-は3.1の頃のものなので、最新版とはちょっと違うところもあることに注意。 (★★★★★)

  • アンドリュー・S・タネンバウム: 分散システム 原理とパラダイム 第2版

    アンドリュー・S・タネンバウム: 分散システム 原理とパラダイム 第2版
    クライアント/サーバシステムを構築する上で必要となる知識が総論されてます。Web技術者も、フレームワーク部分を開発するのであれば必読。 (★★★★★)

  • Joel Spolsky∥著: ジョエル・オン・ソフトウェア

    Joel Spolsky∥著: ジョエル・オン・ソフトウェア
    前述の書籍「ソフトウエア開発プロフェッショナル」をより砕いたもの、という感じでしょうか。 前書きではプログラマでなくSE向けの本のように書かれているが、プログラマが読んでも面白い本であると思われます。 SEになった新人(あるいはそういう会社に入る/入りたての人)にとっては、これからどういったことが仕事を遂行していく上で起こりえるのか、どのように考えて行なっていけばいいのか決定する助けになると思います。 元は″Joel on Software″というブログの記事で、web上でも一部日本語で読めます。 http://japanese.joelonsoftware.com/ (★★★)

  • ドナルド・C・ゴース,ジェラルド・M・ワインバーグ: ライト、ついてますか

    ドナルド・C・ゴース,ジェラルド・M・ワインバーグ: ライト、ついてますか
    問題解決(一昔前のの流行語で言うところの『ソリューション』)能力は、システムエンジニアのスキルとして備えるべきもののうちのひとつです。しかし、これは難しい。学校で出されるテストと違い、唯一の、(問題提出者が想定している)解を求めるだけが「問題解決」では無いからです。そもそも、何が問題なのか、それは本当に問題なのか、それは本当に解決すべき問題なのか、その問題解決方法は正しいのか、などを解決しなければ、「その解は正しいのか」に辿りつくことができません。この本の最も良いところのひとつは、本があまり厚くないこと。すぐに読めるし、何回も読み返す気になるでしょう。 (★★★★★)

  • スティーブ・マコネル: ソフトウエア開発プロフェッショナル

    スティーブ・マコネル: ソフトウエア開発プロフェッショナル
    コードコンプリートで有名なスティーブマコネルの著書。新人SEに読んで欲しい。個人として業界の中でどうあるべきか、組織としてどうあるべきか、SEのプロ意識とは?SEの心構え概論、といったところでしょうか。また、業界における資格の重要性についても説かれています。この業界では資格が特に軽んじられる傾向がありますが、この傾向はどんな弊害をもたらすのか、将来的にこの業界は資格に対してどのような姿勢で臨んでいくべきなのか。日経BP社では(他の出版社もだが)最近、似たような類いのあまり面白くない書籍が乱出版されていますが、この本は別格だと思うので安心して購入して欲しいと思います。 (★★★★★)

無料ブログはココログ