Play! でモデルを作成するときにちょっとはまった

Play!のチュートリアルを一通り終えたので早速自分の作りたいものを作ろうと意気込んでおります。とりあえず、技術を学ぶためにはこれまで慣れ親しんだものを再度作り直すことが比較する上でも、理解する上でも良いと思うのでまたまた経費精算を作成します。これでRails、ASP.NET MVC3、Play!と作ることになりますが…。

で、早速エンティティを.NETから移行しようとしたのですがはまってしまいました。まさにのっけから躓いた感じですね。

具体的には、下記のようなモデルを作成しました。

[sourcecode language="java"] package models;

import org.joda.time.DateTime; import play.data.validation.Required; import play.db.jpa.Model;

import javax.persistence.*;

@Entity @Table(name="account_code") public class AccountCode extends Model {

@Id
@Required
public long id;

@Column(name="code")
public String code;

@Column(name="data_name")
public String name;

@Column(name="memo")
public String memo;

@Column(name="taishaku_div")
public long taishakuDivId;

@Column(name="sort_order")
public int sortOrder;

@Column(name="zeikbn_ac_id")
public long zeikbnAcId;

@Column(name="kamoku_kbn_id")
public long kamokuKbnId;

@Column(name="version_id")
@Version
public int timestamp;

@Column(name="team_id")
public long teamId;

@Column(name="del_flg")
public boolean delFlg;

@Column(name="reg_user_id")
public long regUserId;

@Column(name="upd_user_id")
public long updUserId;

@Column(name="reg_date_time")
public DateTime regDateTime;

@Column(name="upd_date_time")
public DateTime updDateTime;

}

[/sourcecode]

テストはこのようにインスタンスが生成されるか確認するだけの単純なものです。

[sourcecode language="java"] public class AccountCodeTest extends UnitTest { @Test public void accountCodeが生成できる(){ AccountCode ac = new AccountCode(); assertNotNull(ac); } } [/sourcecode]

とりあえず、テストでインスタンスが生成されるか確認したところ、テストはパスするけれども例外がスローされるという現象が発生しました。

id_error

テストは確かに正常に通っています。ただ、エラーが発生しているという状況です。エラーの内容を確認すると下記のようになっていることがわかります。

[sourcecode language="bash"]

22:25:52,500 ERROR ~ Javassist Enhancement failed: models.AccountCode java.lang.RuntimeException: duplicate method: getId in models.AccountCode$$javassist_0 at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:344) at javassist.util.proxy.ProxyFactory.createClass2(ProxyFactory.java:314) at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.java:273) at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.getProxyFactory(JavassistLazyInitializer.java:162) at org.hibernate.proxy.pojo.javassist.JavassistProxyFactory.postInstantiate(JavassistProxyFactory.java:65) at org.hibernate.tuple.entity.PojoEntityTuplizer.buildProxyFactory(PojoEntityTuplizer.java:185) at org.hibernate.tuple.entity.AbstractEntityTuplizer.<init>(AbstractEntityTuplizer.java:167) at org.hibernate.tuple.entity.PojoEntityTuplizer.<init>(PojoEntityTuplizer.java:77) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:525) at org.hibernate.tuple.entity.EntityTuplizerFactory.constructTuplizer(EntityTuplizerFactory.java:105) at org.hibernate.tuple.entity.EntityTuplizerFactory.constructDefaultTuplizer(EntityTuplizerFactory.java:133) at org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping.<init>(EntityEntityModeToTuplizerMapping.java:80) at org.hibernate.tuple.entity.EntityMetamodel.<init>(EntityMetamodel.java:322) at org.hibernate.persister.entity.AbstractEntityPersister.<init>(AbstractEntityPersister.java:485) at org.hibernate.persister.entity.SingleTableEntityPersister.<init>(SingleTableEntityPersister.java:133) at org.hibernate.persister.PersisterFactory.createClassPersister(PersisterFactory.java:84) at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:286) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1842) at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:902) at play.db.jpa.JPAPlugin.onApplicationStart(JPAPlugin.java:240) at play.plugins.PluginCollection.onApplicationStart(PluginCollection.java:480) at play.Play.start(Play.java:515) at play.test.PlayJUnitRunner.<init>(PlayJUnitRunner.java:31) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:525) at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:31) at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:24) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57) at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57) at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24) at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:33) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:43) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) Caused by: javassist.bytecode.DuplicateMemberException: duplicate method: getId in models.AccountCode$$javassist_0 at javassist.bytecode.ClassFile.testExistingMethod(ClassFile.java:593) at javassist.bytecode.ClassFile.addMethod(ClassFile.java:577) at javassist.util.proxy.ProxyFactory.override(ProxyFactory.java:658) at javassist.util.proxy.ProxyFactory.overrideMethods(ProxyFactory.java:632) at javassist.util.proxy.ProxyFactory.make(ProxyFactory.java:552) at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:335) ... 44 more 22:25:52,524 WARN  ~ could not create proxy factory for:models.AccountCode org.hibernate.HibernateException: Javassist Enhancement failed: models.AccountCode at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.getProxyFactory(JavassistLazyInitializer.java:169) at org.hibernate.proxy.pojo.javassist.JavassistProxyFactory.postInstantiate(JavassistProxyFactory.java:65) at org.hibernate.tuple.entity.PojoEntityTuplizer.buildProxyFactory(PojoEntityTuplizer.java:185) at org.hibernate.tuple.entity.AbstractEntityTuplizer.<init>(AbstractEntityTuplizer.java:167) at org.hibernate.tuple.entity.PojoEntityTuplizer.<init>(PojoEntityTuplizer.java:77) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:525) at org.hibernate.tuple.entity.EntityTuplizerFactory.constructTuplizer(EntityTuplizerFactory.java:105) at org.hibernate.tuple.entity.EntityTuplizerFactory.constructDefaultTuplizer(EntityTuplizerFactory.java:133) at org.hibernate.tuple.entity.EntityEntityModeToTuplizerMapping.<init>(EntityEntityModeToTuplizerMapping.java:80) at org.hibernate.tuple.entity.EntityMetamodel.<init>(EntityMetamodel.java:322) at org.hibernate.persister.entity.AbstractEntityPersister.<init>(AbstractEntityPersister.java:485) at org.hibernate.persister.entity.SingleTableEntityPersister.<init>(SingleTableEntityPersister.java:133) at org.hibernate.persister.PersisterFactory.createClassPersister(PersisterFactory.java:84) at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:286) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1842) at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:902) at play.db.jpa.JPAPlugin.onApplicationStart(JPAPlugin.java:240) at play.plugins.PluginCollection.onApplicationStart(PluginCollection.java:480) at play.Play.start(Play.java:515) at play.test.PlayJUnitRunner.<init>(PlayJUnitRunner.java:31) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:525) at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:31) at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:24) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57) at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:29) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:57) at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:24) at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:33) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:43) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:195) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:63) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) Caused by: java.lang.RuntimeException: duplicate method: getId in models.AccountCode$$javassist_0 at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:344) at javassist.util.proxy.ProxyFactory.createClass2(ProxyFactory.java:314) at javassist.util.proxy.ProxyFactory.createClass(ProxyFactory.java:273) at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.getProxyFactory(JavassistLazyInitializer.java:162) ... 41 more Caused by: javassist.bytecode.DuplicateMemberException: duplicate method: getId in models.AccountCode$$javassist_0 at javassist.bytecode.ClassFile.testExistingMethod(ClassFile.java:593) at javassist.bytecode.ClassFile.addMethod(ClassFile.java:577) at javassist.util.proxy.ProxyFactory.override(ProxyFactory.java:658) at javassist.util.proxy.ProxyFactory.overrideMethods(ProxyFactory.java:632) at javassist.util.proxy.ProxyFactory.make(ProxyFactory.java:552) at javassist.util.proxy.ProxyFactory.createClass3(ProxyFactory.java:335) ... 44 more

[/sourcecode]

どうも、同一メソッドが複数定義されていることで例外がスローされているようです。該当するメソッドは getId とのことなので、Idフィールドが悪さをしていると考えられます。

困ったときはチュートリアルに戻るのが基本です。チュートリアルでエンティティを作成している部分を確認してみると、この件に該当すると思われる内容が記載されていました。

もし以前に JPA を使ったことがあるなら、すべての JPA エンティティは @Id プロパティを提供しなければならないことを知っているでしょう。ここでは、Model スーパークラスが自動的に生成された数値型の id を提供しており、ほとんどの場合はこれで必要充分です。 Play framework - Documentation

これより、idというフィールドを宣言したことによって自動生成されるidフィールドに対応するgetter、setterとバッティングしているらしいと言うことがわかりました。

そこで、試しに idフィールドを削除した上で実際に実行してみると例外がスローされることなく正常に処理が終了しました。また、テストもグリーンとなりました。

id_error2

併せて、データベースを確認したところ確かにid列が生成されていました。

id_error3

ということで、エンティティにidという名前のフィールドは宣言してはいけないということがわかりました。チュートリアルって実際に試しているのですが、理解が進んでいるところとそうでないところが多いため、やはり何か能動的に作ってみることをしないとこういう気づきは得られづらいですね。