JDOのモデルの状態を理解しよう

GAEのJDOのモデルは、ざっくりいうと4種類の状態があります(本当はもっといっぱいあるけど、4つをおさえておけば普通はOK)。

  • transient
  • persistent
  • detached
  • hollow

モデルをnewして、PersistenceManager#makePersistent()を呼ぶまでは、transientです。
PersistenceManager#makePersistent()がよばれるとpersistentな状態です。persistentなモデルの状態は、PersistenceManagerをcloseするかTransactionをcommitしたときに、データストアに反映されます。


PersistenceManagerをcloseしたときからが、運命の分かれ目。何の設定もしないとhollowになり、lazy loadingされているオブジェクトに触ろうとするとエラーになります。
クエリの結果のリストを触ろうとして、「ObjectManager has been closed」のエラーが出た人がいるかもしれません。これがまさしくその状態。
hollowは、JDOの定義によるとトランザクションがコミットされた後のモデルの状態なんだけど、GAE的には上記のように理解したほうがよさげ。JDOの定義は、Java EE環境で実行されていて、トランザクションがコミットされたときに、PersistenceManagerが同時にcloseされるから、あのような説明なんだと思います。


hollowの状態を避けるためには、PersistenceManagerをcloseする前に、PersistenceManager#detachCopy() or PersistenceManager#detachCopyAll()を呼び出す必要があります。これらのメソッドを呼び出した後は、PersistenceManagerをcloseした後も安全に触ることができます。


detachCopyを呼び出した後は、モデルの定義によって二つの状態に分かれます。detachedとtransient。
detachedなモデルは、セッションなどに入れておいて、後でmakePersistent()するとpersistentな状態に戻ります。
transientなモデルは、二度とpersistentな状態には戻れません。makePersistent()するとエラーになります。キーを消してmakePersistent()すると別のモデルとして、永続化されます。


detach可能にするためには、モデルの定義を次のようにします。detachable="true"がポイント。trueなのに文字列なのもポイント(笑)。


@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")


PersistenceManagerを呼ぶ前に毎回detachCopyを呼び出すのはめんどくさいですよね。そのために用意されているのが、jdoconfig.xmlのDetachOnCloseとDetachOnCommitの設定。


<property name="datanucleus.DetachOnClose" value="true"/>
<property name="datanucleus.DetachOnCommit" value="true"/>
GAEだとあまりトランザクションを使わないので、DetachOnCloseのほうがよさげ。


複数のリクエストにまたがって、detachedなモデルを維持するためには、セッションに入れておく必要があります。そのためには、Serializableをimplementsしましょう。implementsしないとエラーもはかずにモデルが消えます。はまりどころです(笑)。


モデルをセッションにおきたくないと思う人もいることでしょう。私もそう。その場合は、formのhiddenなどにキーを入れておいて、PersistenceManager#getObjectById()で取得したpersistentなモデルに入力された値を反映させます。
私はこっちのほうが好み。


JSPにモデルを直接渡してしまうと、unownedな関連があったときに、keyオブジェクトから関連先を解決する必要があり、大変なことになります。そのため、JSPには、いったんモデルからMapにつめて渡すのがお勧めです。Mapにつめるときに、View用にいろいろ変換できるので、ちょっとめんどくさいようですが、そのほうが確実です。


これまでのやり方だと、複数の人が同じモデルを更新したときに、後勝ちになります。これを防ぐのが、@Versionによる楽観的排他制御。デフォルトでは楽観的排他制御は行われません。@Versionを次のように指定する必要があります。


@Version(strategy = VersionStrategy.VERSION_NUMBER)
detachableなモデルなら、楽観的排他制御は、JDOが自動的に行います。detachableでないモデルは、JDOHelper#getVersion()でモデルのversionの値がLongで取得できるので、それを使って自前で、楽観的排他制御を行います。
Slim3には、 T getObjectById(T model, long version)を用意して、Slim3側で楽観的排他制御を行う機能を追加する予定です。