2009年9月12日土曜日

Object Manager has been closed

GAE/Jベースで、検索するメソッドのテストケースを書いていたら実行時に以下の例外が出た。
Object Manager has been closed
org.datanucleus.exceptions.NucleusUserException: Object Manager has been closed
 at org.datanucleus.ObjectManagerImpl.assertIsOpen(ObjectManagerImpl.java:3876)
 at org.datanucleus.ObjectManagerImpl.getFetchPlan(ObjectManagerImpl.java:376)
 at org.datanucleus.store.query.Query.getFetchPlan(Query.java:497)
 at org.datanucleus.store.appengine.query.DatastoreQuery$5.apply(DatastoreQuery.java:508)
 at org.datanucleus.store.appengine.query.DatastoreQuery$5.apply(DatastoreQuery.java:507)
 at org.datanucleus.store.appengine.query.StreamingQueryResult.resolveNext(StreamingQueryResult.java:137)
 at org.datanucleus.store.appengine.query.StreamingQueryResult$1.computeNext(StreamingQueryResult.java:163)
 at org.datanucleus.store.appengine.query.AbstractIterator.tryToComputeNext(AbstractIterator.java:132)
 at org.datanucleus.store.appengine.query.AbstractIterator.hasNext(AbstractIterator.java:127)
 at org.datanucleus.store.appengine.query.StreamingQueryResult$AbstractListIterator.hasNext(StreamingQueryResult.java:229)
 at jp.co.tricell.sonicboom.rpc.test.TestMutterUtils.testGetMutterOf(TestMutterUtils.java:44)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at junit.framework.TestCase.runTest(TestCase.java:164)
 at junit.framework.TestCase.runBare(TestCase.java:130)
 at junit.framework.TestResult$1.protect(TestResult.java:106)
 at junit.framework.TestResult.runProtected(TestResult.java:124)
 at junit.framework.TestResult.run(TestResult.java:109)
 at junit.framework.TestCase.run(TestCase.java:120)
 at junit.framework.TestSuite.runTest(TestSuite.java:230)
 at junit.framework.TestSuite.run(TestSuite.java:225)
 at org.eclipse.jdt.internal.junit.runner.junit3.JUnit3TestReference.run(JUnit3TestReference.java:130)
 at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:460)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:673)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:386)
 at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
むぅーと思っていたら、ひがさんのブログでちょうど参考になる記事発見。
参考:JDOのモデルの状態を理解しよう - ひがやすを blog

テスト元メソッドは以下。つまりPersistenceManagerをclose()したのに、テストケースの方でクエリの結果を触ろうとしていたので「Object Manager has been closed」が出たということ。
// クラスのアノテーションを記述
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Mutter {
・・・・・
}

public class MutterUtils {
・・・・・

 public List<Mutter> getMutterOf(String myKey) {
  PersistenceManager pm = PMF.get().getPersistenceManager();

  Query query = pm.newQuery(Mutter.class);
  query.declareParameters("String myKey");
  query.setFilter("userName == myKey");
  query.setOrdering("date DESC");
  List<Mutter> mutters = (List<Mutter>) query.execute(myKey);

  pm.close();

  return mutters;
 }
}
ひがさんの記事を参考に、クラスのアノテーションに太字部分を追加。
@PersistenceCapable(identityType = IdentityType.APPLICATION, detachable="true")。

これでPersistenceManagerをclose()した後でも触れるdetachedなモデルになった。

さらに、PersistenceManager#detachCopy()やPersistenceManager#detachCopyAll()を呼ぶのが面倒なので、jdoconfig.xmlに以下のpropertyタグを追加。
<property name="datanucleus.DetachOnClose" value="true"/>

あわせて、JDOのモデルには以下の4パターン
  • transient
  • persistent
  • detached
  • hollow
があるということも学んだゾ。