Skip to content
This repository has been archived by the owner on Sep 12, 2018. It is now read-only.

PlayFrameworkTroubleShooting

Yi EungJun edited this page Jan 8, 2015 · 10 revisions

sbt 0.11.3 에러

sbt 0.11.3 이 저장소에 없다고 하면서 안되길래, 다음과 같이 수정한 것이 원인이 아닐까 의심했다.

framework/build에서,

-PLAY_VERSION="2.0-SNAPSHOT"
+PLAY_VERSION="2.0.4"

framework/sbt/play.boot.properties에서,

-  version: 2.0-SNAPSHOT
+  version: 2.0.4

그래서 모든 수정을 원래대로 돌려놓으니(git stash) 놀랍게도 잘 동작했다. 그런데 이상한 것은 한번 잘 되기 시작하면 다시 2.0.4로 돌려놓아도 잘 된다는 점이다.

sbt.PlayExceptions$CompilationException: Compilation error [value LabelApp is not a member of object controllers.routes]

해결책: 콘트롤러 메소드는 반드시 static 이어야 한다.

Cannot register class [models.task.TaskBoard] in Ebean server

Bad type on operand stack in method models.task.TaskBoard.getUser()Lorg/codehaus/jackson/JsonNode; at offset 22라는 에러가 원인이 되어 발생했다.

원인: 모델의 property에 대한 getter로 착각될 수 있는 메소드를 만들었다. 위의 getUser() 메소드는 persistent property인 'user'를 얻으려고 의도하여 만든 것이 아니므로, @Transient를 붙여주어야 한다.

간혹 clean-all로 해결되는 경우도 있다.

원인2: play 2.1.x의 버그

모델과는 아무런 상관도 없는 클래스의 메소드에서 이 에러가 난 일이 있다. @Transient도 안통함. See ~/mysrc/test/play/badtype

[RuntimeException: java.lang.VerifyError: Bad type on operand stack in method name.fraser.neil.plaintext.diff_match_patch.diff_cleanupSemanticLossless(Ljava/util/LinkedList;)V at offset 98]

play 2.2.0에서는 문제가 생기지 않는다.

Cannot register class [models.Watch] in Ebean server

play.api.Configuration$$anon$1: Configuration error[Cannot register class [models.Watch] in Ebean server]
        at play.api.Configuration$.play$api$Configuration$$configError(Configuration.scala:80) ~[play_2.10.jar:2.1.0]
        at play.api.Configuration.reportError(Configuration.scala:558) ~[play_2.10.jar:2.1.0]
        at play.Configuration.reportError(Configuration.java:298) ~[play_2.10.jar:2.1.0]
        at play.db.ebean.EbeanPlugin.onStart(EbeanPlugin.java:71) ~[play-java-ebean_2.10.jar:2.1.0]
        at play.api.Play$$anonfun$start$1$$anonfun$apply$mcV$sp$1.apply(Play.scala:63) ~[play_2.10.jar:2.1.0]
        at play.api.Play$$anonfun$start$1$$anonfun$apply$mcV$sp$1.apply(Play.scala:63) ~[play_2.10.jar:2.1.0]
Caused by: java.lang.NoClassDefFoundError: com/avaje/ebean/bean/EnhancedTransactional
        at java.lang.ClassLoader.defineClass1(Native Method) ~[na:1.7.0_45]
        at java.lang.ClassLoader.defineClass(ClassLoader.java:800) ~[na:1.7.0_45]
        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) ~[na:1.7.0_45]
        at java.net.URLClassLoader.defineClass(URLClassLoader.java:449) ~[na:1.7.0_45]
        at java.net.URLClassLoader.access$100(URLClassLoader.java:71) ~[na:1.7.0_45]
        at java.net.URLClassLoader$1.run(URLClassLoader.java:361) ~[na:1.7.0_45]
Caused by: java.lang.ClassNotFoundException: com.avaje.ebean.bean.EnhancedTransactional
        at java.net.URLClassLoader$1.run(URLClassLoader.java:366) ~[na:1.7.0_45]
        at java.net.URLClassLoader$1.run(URLClassLoader.java:355) ~[na:1.7.0_45]
        at java.security.AccessController.doPrivileged(Native Method) ~[na:1.7.0_45]
        at java.net.URLClassLoader.findClass(URLClassLoader.java:354) ~[na:1.7.0_45]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:425) ~[na:1.7.0_45]
        at java.lang.ClassLoader.loadClass(ClassLoader.java:358) ~[na:1.7.0_45]

See https://github.com/playframework/playframework/issues/1528

play-2.1.1 이상 버전을 쓰지 말고 play-2.1.0을 쓰면 문제가 재현되지 않기도 한다.

No ScalarType registered for class models.enumeration.State

data binding을 하려고 하는데, type이 맞는 것이 없는 경우 발생한다.

사례1: 사용자의 query string을 model에 바인딩하려고 하는데, query string의 'state' 필드에 들어있는 값은 State 타입에 대한 것인데, model의 'state' 필드는 타입이 IssueState인 경우 발생했다.

사례2: el.in(sp.getField(), value); value는 Set이고 field의 타입은 Long인 경우임에도 발생했다. No ScalarType registered for class java.util.LinkedHashSet

ManyToMany 관계에서 한쪽을 삭제했을 때 외래키 에러 발생

원인: junction table에서 그 삭제한 쪽을 참조하고 있기 때문에 에러가 발생한다.

해결책: cascade를 설정하여 junction table의 row도 같이 삭제되도록 한다.

@ManyToMany(cascade = CascadeType.ALL)
public Set<User> receivers;

bean 삭제시 존재하지 않는 컬럼을 찾으려다 에러가 발생함

사례: project.delete()로 프로젝트를 삭제하려고 했는데 어째선지 IssueLabel entity에서 issue_id 컬럼을 찾으려 시도했다. IssueLabel과 Issue는 양방향으로 ManyToMany 관계이기 때문에 IsssueLabel에 issue_id 컬럼을 갖고 있지 않아 에러가 발생했다.

[info] Test models.ProjectTest.isOnlyManager ignored
[error] Test models.ProjectTest.delete failed: Query threw SQLException:Column "ISSUE_ID" not found; SQL statement:
[error] select t0.id c0
[error] from issue_label t0
[error] where (issue_id) in (?,?,?,?,?,?,?)  [42122-158]
[error] Bind values:[null]
[error] Query was:
[error] select t0.id c0
[error] from issue_label t0
[error] where (issue_id) in (?,?,?,?,?,?,?)

Issue에서 IssueLabel로의 ManyToMany annotation에서 CascadeType=ALL 을 제거하자 해결되었다.

불필요한 cascading을 삼가야 한다. 예를 들어, 다음의 cascading은 잘못된 것이다.

class Milestone extends Model {
    ...
    @OneToMany(cascade = CascadeType.ALL)
    public Set<Issue> issues;
    ...
}

CascadeType.ALL은 REMOVE를 포함하므로, 마일스톤이 삭제되면 그에 속한 이슈들도 모두 삭제된다. 그러나 실제로 그렇게 동작하는 것을 의도하지는 않았을 것이다. 따라서 cascade를 삭제하거나 REMOVE, ALL 외의 다른 것을 써야 한다.

유사에러 - bean 삭제시 에러 발생

[error] Test models.ProjectTest.delete failed: Query threw SQLException:Parameter "#1" is not set; SQL statement:
[error] select t0.id c0, t0.assignee_id c1 
[error] from issue t0 
[error] where t0.id in (?)  [90012-168] 
[error] Bind values:[] 
[error] Query was:
[error] select t0.id c0, t0.assignee_id c1 
[error] from issue t0 
[error] where t0.id in (?)  
[error] 
[error]     at com.avaje.ebeaninternal.server.query.CQuery.createPersistenceException(CQuery.java:815)
[error]     at com.avaje.ebeaninternal.server.query.CQuery.createPersistenceException(CQuery.java:795)
[error]     at com.avaje.ebeaninternal.server.query.CQueryEngine.findMany(CQueryEngine.java:210)
[error]     at com.avaje.ebeaninternal.server.query.DefaultOrmQueryEngine.findMany(DefaultOrmQueryEngine.java:77)
[error]     at com.avaje.ebeaninternal.server.core.OrmQueryRequest.findList(OrmQueryRequest.java:272)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.findList(DefaultServer.java:1502)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:518)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:563)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.deleteManyDetails(DefaultPersister.java:1176)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.deleteAssocMany(DefaultPersister.java:1143)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:624)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:452)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1867)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1857)
[error]     at com.avaje.ebean.Ebean.delete(Ebean.java:648)
[error]     at models.ProjectTest.delete(ProjectTest.java:71)
[error]     ...
[error] Caused by: org.h2.jdbc.JdbcSQLException: Parameter "#1" is not set; SQL statement:
[error] select t0.id c0, t0.assignee_id c1 
[error] from issue t0 
[error] where t0.id in (?)  [90012-168]
[error]     at org.h2.message.DbException.getJdbcSQLException(DbException.java:329)
[error]     at org.h2.message.DbException.get(DbException.java:169)
[error]     at org.h2.message.DbException.get(DbException.java:146)
[error]     at org.h2.expression.Parameter.checkSet(Parameter.java:73)
[error]     at org.h2.command.Prepared.checkParameters(Prepared.java:163)
[error]     at org.h2.command.CommandContainer.query(CommandContainer.java:85)
[error]     at org.h2.command.Command.executeQuery(Command.java:191)
[error]     at org.h2.jdbc.JdbcPreparedStatement.executeQuery(JdbcPreparedStatement.java:109)
[error]     at com.jolbox.bonecp.PreparedStatementHandle.executeQuery(PreparedStatementHandle.java:172)
[error]     at com.avaje.ebeaninternal.server.query.CQuery.prepareBindExecuteQuery(CQuery.java:382)
[error]     at com.avaje.ebeaninternal.server.query.CQueryEngine.findMany(CQueryEngine.java:174)
[error]     ... 46 more

ManyToOne 인 field를 null로 설정하기

unittest중에는, 어째서인지 setter를 사용하지 않고서는 ManyToOne인 field를 persistent하게 null로 설정할 수 없다. (optional=true도 아무런 도움이 안됨) 이유는 알 수 없다.

unittest중에는 반드시 getter를 사용해야만 하는 경우도 있다.

assertThat(issue.assignee.user.id).isEqualTo(1l); // Fail. you MUST use getter.
assertThat(issue.assignee.getUser().id).isEqualTo(1l); // Success.

bean 삭제시 외래키 참조 무결성 에러

OneToMany와 ManyToOne으로 bidirectional 한 관계인 두 entity가 있을 때, One 쪽의 entity가 삭제되면 자동으로 Many쪽의 entity에서 One쪽의 entity에 대한 참조도 사라져야 할 것 같은데 그렇게 되지 않는다. 결국 이로 인해 참조 무결성 에러가 발생한다.

[error] Test models.MilestoneTest.delete failed: ERROR executing DML bindLog[] error[Referential integrity constraint violation: "FK_ISSUE_MILESTONE_10: PUBLIC.ISSUE FOREIGN KEY(MILESTONE_ID) REFERENCES PUBLIC.MILESTONE(ID) (1)"; SQL statement:\n delete from milestone where id=? and title=? and due_date=? and state=? and project_id=? [23503-168]]
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:97)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.delete(DmlBeanPersister.java:48)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersistExecute.executeDeleteBean(DefaultPersistExecute.java:109)
[error]     at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeNow(PersistRequestBean.java:489)
[error]     at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeOrQueue(PersistRequestBean.java:511)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:635)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:452)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1867)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1857)
[error]     at com.avaje.ebean.Ebean.delete(Ebean.java:648)
[error]     at play.db.ebean.Model.delete(Model.java:167)
[error]     at models.Milestone.delete(Milestone.java:60)
[error]     at models.MilestoneTest.delete(MilestoneTest.java:67)
[error]     ...
[error] Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "FK_ISSUE_MILESTONE_10: PUBLIC.ISSUE FOREIGN KEY(MILESTONE_ID) REFERENCES PUBLIC.MILESTONE(ID) (1)"; SQL statement:
[error] delete from milestone where id=? and title=? and due_date=? and state=? and project_id=? [23503-168]
[error]     at org.h2.message.DbException.getJdbcSQLException(DbException.java:329)
[error]     at org.h2.message.DbException.get(DbException.java:169)
[error]     at org.h2.message.DbException.get(DbException.java:146)
[error]     at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:414)
[error]     at org.h2.constraint.ConstraintReferential.checkRowRefTable(ConstraintReferential.java:431)
[error]     at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:307)
[error]     at org.h2.table.Table.fireConstraints(Table.java:871)
[error]     at org.h2.table.Table.fireAfterRow(Table.java:888)
[error]     at org.h2.command.dml.Delete.update(Delete.java:99)
[error]     at org.h2.command.CommandContainer.update(CommandContainer.java:75)
[error]     at org.h2.command.Command.executeUpdate(Command.java:230)
[error]     at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:156)
[error]     at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:142)
[error]     at com.jolbox.bonecp.PreparedStatementHandle.executeUpdate(PreparedStatementHandle.java:203)
[error]     at com.avaje.ebeaninternal.server.type.DataBind.executeUpdate(DataBind.java:55)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DeleteHandler.execute(DeleteHandler.java:62)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:86)
[error]     ... 45 more

bean 삭제시 외래키 참조 무결성 에러 2

[error] Test models.ProjectTest.delete failed: ERROR executing DML bindLog[] error[Referential integrity constraint violation: "FK_ASSIGNEE_PROJECT_2: PUBLIC.ASSIGNEE FOREIGN KEY(PROJECT_ID) REFERENCES PUBLIC.PROJECT(ID)"; SQL statement:\n delete from project where id=? and name=? and overview=? and vcs=? and siteurl=? and logo_path is null and owner=? and share_option=? and is_author_editable=? [23503-158]]
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:116)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.delete(DmlBeanPersister.java:67)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersistExecute.executeDeleteBean(DefaultPersistExecute.java:128)
[error]     at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeNow(PersistRequestBean.java:535)
[error]     at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeOrQueue(PersistRequestBean.java:557)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:654)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:464)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1831)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1821)
[error]     at com.avaje.ebean.Ebean.delete(Ebean.java:678)
[error]     at play.db.ebean.Model.delete(Model.java:142)
[error]     at models.Project.delete(Project.java:70)
[error]     at models.ProjectTest.delete(ProjectTest.java:57)
[error]     ...
[error] Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "FK_ASSIGNEE_PROJECT_2: PUBLIC.ASSIGNEE FOREIGN KEY(PROJECT_ID) REFERENCES PUBLIC.PROJECT(ID)"; SQL statement:
[error] delete from project where id=? and name=? and overview=? and vcs=? and siteurl=? and logo_path is null and owner=? and share_option=? and is_author_editable=? [23503-158]
[error]     at org.h2.message.DbException.getJdbcSQLException(DbException.java:329)
[error]     at org.h2.message.DbException.get(DbException.java:169)
[error]     at org.h2.message.DbException.get(DbException.java:146)
[error]     at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:398)
[error]     at org.h2.constraint.ConstraintReferential.checkRowRefTable(ConstraintReferential.java:415)
[error]     at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:291)
[error]     at org.h2.table.Table.fireConstraints(Table.java:861)
[error]     at org.h2.table.Table.fireAfterRow(Table.java:878)
[error]     at org.h2.command.dml.Delete.update(Delete.java:98)
[error]     at org.h2.command.CommandContainer.update(CommandContainer.java:71)
[error]     at org.h2.command.Command.executeUpdate(Command.java:212)
[error]     at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:143)
[error]     at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:129)
[error]     at com.jolbox.bonecp.PreparedStatementHandle.executeUpdate(PreparedStatementHandle.java:203)
[error]     at com.avaje.ebeaninternal.server.type.DataBind.executeUpdate(DataBind.java:74)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DeleteHandler.execute(DeleteHandler.java:80)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:105)
[error]     ... 81 more

bean 삭제시의 에러 3

[error] Test models.IssueTest.delete failed: Data has changed. updated [0] rows sql[delete from assignee where id=? and user_id is null and project_id is null] bind[null]
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlHandler.checkRowCount(DmlHandler.java:123)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DeleteHandler.execute(DeleteHandler.java:81)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:105)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.delete(DmlBeanPersister.java:67)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersistExecute.executeDeleteBean(DefaultPersistExecute.java:128)
[error]     at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeNow(PersistRequestBean.java:535)
[error]     at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeOrQueue(PersistRequestBean.java:557)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:654)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.delete(DefaultPersister.java:464)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1831)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.delete(DefaultServer.java:1821)
[error]     at com.avaje.ebean.Ebean.delete(Ebean.java:678)
[error]     at play.db.ebean.Model.delete(Model.java:142)
[error]     at models.Assignee.deleteIfEmpty(Assignee.java:76)
[error]     at models.Issue.delete(Issue.java:232)
[error]     at models.IssueTest.delete(IssueTest.java:75)
[error]     ...

getGeneratedSql()이 null을 반환

getGeneratedSql()로 sql 쿼리문을 얻으려면 해당 쿼리가 실행된 상태여야 한다. 즉 findList()와 같은 메소드를 실행해주어야 한다.

    ExpressionList<T> el = makeExpressionList(mop, msp, finder);
    Query<T> q = el.query();
    Page<T> result = q.findPagingList(pageSize).getPage(page);
    q.findList();

    Logger.debug(q.getGeneratedSql());

Can not find Master [class models.NonIssue] in Child[models.Comment]

ManyToMany가 아닌 association이 있을 때는 반드시 양방향으로 참조를 해야 하는 것을 보인다. 이 때 property 이름은 mappedBy와 같거나 그렇게 하지 않으면 BeanPropertyAssocMany.java 의 initChildMasterProperty 메소드에서 마스터를 찾다가 에러를 낸다.

    // find the property in the many that matches
    // back to the master (Order in the OrderDetail bean)
    childMasterProperty = initChildMasterProperty();

unidirectional이 아니라면 master가 꼭 필요하다. mappedBy가 없을 때만 unidirectional인 것 같다.

@OneToMany 의 경우 mappedBy의 default는 ""인데, 이상하게 항상 bidirectional인 것 같다.

ebean이 지원하는 Inheritance 전략은 SINGLE_TABLE 뿐인가?

See http://www.avaje.org/limitation.html

Only Single Table Inheritance

Current there is only support for single table inheritance. The other two inheritance strategies are considered Enhancement requests and will be introduced in a feature release.

그럴거면 다른 전략을 사용하려고 했을 때 exception을 던져야 되는 거 아닌가?

그런데 실제로는 Inheritance annotation 생략시 TABLE_PER_CLASS으로 동작하는 것 같다. Entity 별로 테이블이 하나씩 생겨났다.

entity에 Inheritance를 설정했을 때 DB 에러가 발생한다면 (테이블이 없다라던가)

evolution script가 생성되지 않을 것일 수 있다. db/evolutions/default/*.sql를 삭제해 볼 것.

github에서 받은 Play를 사용할 수 없다.

다음과 같은 에러가 출력되면서 콘솔이 실행되지 않는다.

This project uses Play 2.1-RC2!
Update the Play sbt-plugin version to 2.1-SNAPSHOT (usually in project/plugins.sbt)

일단 시키는 대로 해보면, sbt.ResolveException: unresolved dependency: play#sbt-plugin;2.1-SNAPSHOT: not found 예외를 만나게 된다.

그냥 공식사이트에서 다운로드 받는 게 편하다.

[error] Test utils.AccessControlTest.isAuthor failed: Database 'default' is in inconsistent state![An evolution has not been applied properly. Please check the problem and resolve it manually before marking it as resolved.]

primary key(id)가 없는 entity가 있는지 확인할 것.

컴파일 무한반복

Cannot load the JNotify native library (null)
Play will check file changes for each request, so expect degraded reloading performace.

play run으로 실행했을 때 무한히 컴파일을 반복하는 문제가 발생하는 경우가 있다. play start로 실행하면 그런 문제가 발생하지 않는다.

play clean-all하고 다시 빌드하면, 여전히 위의 메시지는 나타나지만 빌드가 무한반복되는 문제는 사라진다.

다음의 exception이 원인인 것으로 보인다.

Caused by: java.lang.RuntimeException: Error initializing fshook_inotify library. linux error code #24, man errno for more info
    at net.contentobjects.jnotify.linux.JNotify_linux.<clinit>(Unknown Source)
    ... 61 more

에러코드 #24는 EMFILE(Too many open files)인 것으로 보인다.

[IllegalStateException: No value]

다음과 같은 코드에서 에러 발생

Form<IssueComment> commentForm = new Form<IssueComment>(IssueComment.class)
    .bindFromRequest();
final IssueComment comment = commentForm.get();

DB가 생기다 말아서 발생하는 문제들.

어떤 테이블은 생기고 어떤 테이블은 안 생긴 상태기 때문에, 종잡을 수 없는 에러들이 발생하게 된다.

만약 DB가 생기다 말았다는 것을 인지하지 못한다면 원인을 찾는데 불필요한 수고를 많이 들이게 될 가능성이 높다.

ebean에서는 transient getter를 지원하지 않는다

@Transient on methods

If you are using Ebean enhancement (Field Access) then you can put the @Transient
annotation on a method and it is NOT intercepted. Specifically the method is left exactly
as it is (rather than having the GETFIELD PUTFIELD byte codes replaced with
intercepted method calls).

2011년에 지원하지 않는다는 글이 메일링 리스트에 올라왔었는데, 지금도 해보면 여전히 안됨. 문서에는 별도로 언급되지 않았음.

https://groups.google.com/forum/?fromgroups=#!searchin/ebean/getter/ebean/TpAmolj9_OU/rGGkIbwhLtwJ

Ebean이 쿼리의 결과를 정렬할 때, DB에 컬럼으로써 존재하지 않는 field를 기준으로 삼을 수 있게 하려면 이게 필요한데 일단 불가능한 것으로 보인다.

parametrised type인 entity가 가능한가

다음과 같은 것이 가능한가?

package models;

import javax.persistence.*;
import play.db.ebean.*;

@Entity
public class Comment<T extends Posting> extends Model {
    @Id
    public Long id;

    @OneToOne
    pubic T container;

    public String body;
};

entity는 만들 수 있지만 container가 null이다. container 라는 필드가 DB에 생기지도 않는다.

어떤 property가 어째선지 계속 null

@Lob 어노테이션이 붙어있다면 반드시 getter로 값을 얻어야 한다.

refresh가 안되는 경우

OneToOne 이외의 relation인 property에는 refresh가 안된다. Ebean.refreshMany를 써서 해결가능.

intellij idea에서 unittest를 실행할 때 에러

javax.persistence.PersistenceException: Error with [models.task.Card] It has not been enhanced but it's superClass [class play.db.ebean.Model] is? (You are not allowed to mix enhancement in a single inheritance hierarchy) marker[play.db.ebean.Model] className[models.task.Card]

@Entity인 클래스가 superclass를 갖고 있다면 발생하는 에러. 아마 enhanced 되어야 할 것 같은 클래스가 subclassing되어있는 것으로 보여서 생기는 문제인듯. play의 경우 모든 entity가 Model을 상속하므로 이런 문제가 발생한다. play run으로 실행할때는 문제없도록 모종의 조치를 취하는 모양이다.

컴파일시 거의 확실히 에러가 발생하게 되므로 Run/Debug Configuration의 Before Launch에서 Make는 삭제한다. 컴파일은 play test로 한다.

컴파일러 옵션에 javaagent를 ebeanorm으로 설정해준다.

See http://blog.matthieuguillermin.fr/2012/03/unit-testing-tricks-for-play-2-0-and-ebean/

위 링크에 나온 것과 달리 javaagent로 avaje-ebeanorm-agent-3.1.1.jar 를 써도 잘 동작한다.

설정의 예:

-ea -javaagent:/home/nori/src/Play20/repository/cache/org.avaje.ebeanorm/avaje-ebeanorm-agent/jars/avaje-ebeanorm-agent-3.1.1.jar

unittest에서의 ebean의 bytecode enhanced

unittest에서는 ebean의 bytecode enhanced가 잘 동작하지 않는다. intellij idea에서 실행해보면 다음과 같은 에러를 확인할 수 있다.

transform> cls: play/b/bean/odel  msg: ... ignore field _ebean_intercept

_ebean_intercept가 무시되므로 field access시 preSetter가 실행되지 않고, 따라서 field access로 값을 갱신해도 dirty 상태가 되지 않으므로, update()가 제대로 동작하지 않는다. 그러나 save()로 entity를 생성하는 것은 잘 동작한다. (save()로 갱신은 안됨)

이상하게 테스트를 통해 실행된 것이라 할 지라도, entity에 정의한 메소드에서는 preSetter가 동작한다. 이유는 알 수 없다.

User entity에서 derviated된 NullUser의 경우, id의 기본값이 null인데, field access로 이 값에 접근하면 null을 얻게되는 이상한 현상도 있었다. property access를 사용하면 정상적으로 -1이 반환된다.

unittest에서 play의 setter/getter generation이 동작하지 않을 때

clean-all

deatch?

ebean엔 그런건 없는듯.

– No Attached or Detached Beans

listener

BeanPersistListener 사용방법은 알 수 없다. 문서에 "TODO"라고만 되어있다.

evolution

1.sql을 대체하려고 하는 경우

코드와 DB 스키마의 차이가 발견된 상황에서,

  • 1.sql에 주석으로 매번 대체하라고 되어있음
  • 2.sql이 없다. (불확실)
  • db가 이미 생성되어 있는 경우 (불확실)

nextval

DB migration script를 새로 작성하고 실행하자 이런 에러를 만남.

play.api.UnexpectedException: Unexpected exception[PersistenceException: Error getting sequence nextval]
...
Caused by: javax.persistence.PersistenceException: Error getting sequence nextval
...
Caused by: org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "SELECT POSTING_SEQ.NEXTVAL UNION[*] SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL UNION SELECT POSTING_SEQ.NEXTVAL "; expected "identifier"; SQL statement:
select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval union select posting_seq.nextval [42001-168]
    ...

POSTING_SEQ 가 만들어지지 않아서 발생하는 에러로 보인다.

sequence 이름 바꾸기

rename을 지원하지 않으므로, 새로 만들고 이전 것은 지운다.

CREATE SEQUENCE posting_comment_seq START WITH comment_seq.currval;
DROP SEQUENCE IF EXISTS comment_seq;

문법대로라면 START WITH 뒤에는 long이 와야 하는데 어째선지 위의 쿼리도 잘 동작한다.

@NotNull 과 @Column(nullable=false)

전자는 DB의 컬럼에 not null을 설정하지 않는다.

AnswerMe @NotNull 설정되어 있는 필드에 null로 이미 저장되어 있는 값을 DB에서 읽어들이면 어떻게 되는가? @Column(nullable=false)라면?

ManyToMany 관계를 선언했을 때 NullPointerException 발생

양쪽 모두 mappedBy를 설정한 상태였다. 아마도 mappedBy와 관련된 exception이 발생했는데 이를 처리하는 코드에서 NullPointerException이 발생한 듯.

Cannot connect to database [default]

DB가 손상되었을 수 있다.

그렇다면 우선 recovery 툴로 DB의 덤프를 뜨고,

java -cp ~/src/play-2.1.0/repository/local/com.h2database/h2/1.3.168/jars/h2.jar org.h2.tools.Recover

h2-browser를 통해 적용한다.

play
...
> h2-browser

다음의 SQL 을 실행

RUNSCRIPT FROM 'nforge.h2.sql'

A fatal error has been detected by the Java Runtime Environment:

See FatalErrorOnPlayFramework

OptimisticLockException: Data has changed

다음과 같은 예외가 발생한다면,

[OptimisticLockException: Data has changed. updated [0] rows sql[update attachment set project_id=?, container_type=? where id=? and name=? and hash=? and project_id=? and container_type=? and mime_type=? and size=? and container_id=?] bind[null]]

현재 update하려고 하는 entity가 이미 다른 곳에서 변경되었을 가능성이 있다.

자꾸 존재하는 메소드가 존재하지 않는다며 컴파일 에러가 날 때

다음과 같이 괜히 컴파일 에러가 난다면,

sbt.PlayExceptions$CompilationException: Compilation error[value deleteComment is not a member of controllers.ReverseCodeHistoryApp]
        at sbt.PlayReloader$$anon$2$$anonfun$reload$2$$anonfun$apply$15$$anonfun$apply$16.apply(PlayReloader.scala:349) ~[na:na]
        at sbt.PlayReloader$$anon$2$$anonfun$reload$2$$anonfun$apply$15$$anonfun$apply$16.apply(PlayReloader.scala:349) ~[na:na]
        at scala.Option.map(Option.scala:133) ~[scala-library.jar:na]
        at sbt.PlayReloader$$anon$2$$anonfun$reload$2$$anonfun$apply$15.apply(PlayReloader.scala:349) ~[na:na]
        at sbt.PlayReloader$$anon$2$$anonfun$reload$2$$anonfun$apply$15.apply(PlayReloader.scala:346) ~[na:na]
        at scala.Option.map(Option.scala:133) ~[scala-library.jar:na]

play clean을 해본다.

BeanPropertyAssoc.createPropertyValue에서 NPE

다음과 같은 코드의 마지막 줄에서,

ExpressionList<CodeComment> el1 = CodeComment.find.where().eq("project.id", project.id);
List<CodeComment> comments = el1.findList();

다음과 같은 예외가 발생한다면,

! @6eodpmf2c - Internal server error, for (GET) [/admin/foo/commit/c5c4c2bdb303498e63d42b3fe01e8294ab7d1d09] ->

play.api.Application$$anon$1: Execution exception[[NullPointerException: null]]
        at play.api.Application$class.handleError(Application.scala:289) ~[play_2.10.jar:2.1.0]
        at play.api.DefaultApplication.handleError(Application.scala:383) [play_2.10.jar:2.1.0]
        at play.core.server.netty.PlayDefaultUpstreamHandler$$anon$2$$anonfun$handle$1.apply(PlayDefaultUpstreamHandler.scala:132) [play_2.10.jar:2.1.0]
        at play.core.server.netty.PlayDefaultUpstreamHandler$$anon$2$$anonfun$handle$1.apply(PlayDefaultUpstreamHandler.scala:128) [play_2.10.jar:2.1.0]
        at play.api.libs.concurrent.PlayPromise$$anonfun$extend1$1.apply(Promise.scala:113) [play_2.10.jar:2.1.0]
        at play.api.libs.concurrent.PlayPromise$$anonfun$extend1$1.apply(Promise.scala:113) [play_2.10.jar:2.1.0]
java.lang.NullPointerException: null
        at com.avaje.ebeaninternal.server.deploy.BeanPropertyAssoc.createElPropertyValue(BeanPropertyAssoc.java:126) ~[avaje-ebeanorm-server.jar:na]
        at com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne.buildElPropertyValue(BeanPropertyAssocOne.java:165) ~[avaje-ebeanorm-server.jar:na]
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptor.buildElGetValue(BeanDescriptor.java:1893) ~[avaje-ebeanorm-server.jar:na]
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptor.getElPropertyValue(BeanDescriptor.java:1861) ~[avaje-ebeanorm-server.jar:na]
        at com.avaje.ebeaninternal.server.deploy.BeanDescriptor.getElPropertyDeploy(BeanDescriptor.java:1854) ~[avaje-ebeanorm-server.jar:na]
        at com.avaje.ebeaninternal.server.expression.AbstractExpression.containsMany(AbstractExpression.java:45) ~[avaje-ebeanorm-server.jar:na]

CodeComment의 project 필드에 ManyToOne annotation이 빠져있는 것이 원인일 수 있다.

not found: type Finder

빌드 중에 Finder를 찾을 수 없다며 에러가 발생하기도 함

[info] Compiling 92 Scala sources and 87 Java sources to /home/nori/mysrc/hive/target/scala-2.10/classes... [error] /home/nori/mysrc/hive/app/models/CodeComment.java:25: not found: type Finder [error] public static Finder<Long, CodeComment> find [error] ^ [error] one error found [error] (compile:compile) Compilation failed [error] application -

이건 sbt 버그라고 한다.

package play.test does not exist

다음과 같은 에러가 발생할 수 있는데,

[error] /home/nori/mysrc/hive/app/utils/Config.java:60: error: package play.test does not exist
[error]         Map<String, String> config = play.test.Helpers.inMemoryDatabase();
[error]                                               ^
[error] 1 error
[error] (compile:compile) javac returned nonzero exit code

play.test 패키지는 테스트에서만 사용가능한듯.

[error] (compile:copy-resources) Error wrapping InputStream in GZIPInputStream: java.io.EOFException

play compile 혹은 clean 등을 수행했을 때 만날 수 있는 에러. 소스코드를 nfs로 마운트한 경우에 생길 수 있음.

해결책은 아직 발견되지 않았다.

return type controllers.IssueApp.SearchCondition is not compatible with void

가끔 setter를 직접 만들면 이렇게 문제가 생기는 경우가 있다. 해당 클래스는 model을 상속받지 않았고 entity도 아니었으나 form binding과 querystring binding을 동시에 하고 있었다.

[error] return type controllers.IssueApp.SearchCondition is not compatible with void [error] /home/nori/mysrc/hive/app/controllers/IssueApp.java:74: error: setOrderDir(String) in controllers.IssueApp.SearchCondition cannot override setOrderDir(String) in controllers.AbstractPostingApp.SearchCondition [error] public SearchCondition setOrderDir(String orderDir) { [error] ^ [error] return type controllers.IssueApp.SearchCondition is not compatible with void [error] /home/nori/mysrc/hive/app/controllers/IssueApp.java:79: error: setFilter(String) in controllers.IssueApp.SearchCondition cannot override setFilter(String) in controllers.AbstractPostingApp.SearchCondition [error] public SearchCondition setFilter(String filter) { [error] ^ [error] return type controllers.IssueApp.SearchCondition is not compatible with void [error] /home/nori/mysrc/hive/app/controllers/IssueApp.java:84: error: setPageNum(int) in controllers.IssueApp.SearchCondition cannot override setPageNum(int) in controllers.AbstractPostingApp.SearchCondition [error] public SearchCondition setPageNum(int pageNum) { [error] ^ [error] return type controllers.IssueApp.SearchCondition is not compatible with void [error] 4 errors [error] (compile:compile) javac returned nonzero exit code [error] application -

play clean으로 해결되기도 한다.

id

ebean에는 id가 순서대로 매겨지는 것을 보장한다는 말이 없다. 그리고 sequence로 실제 해보면 순서대로 매겨지지 않는다. autoincrement라면 순서대로 될지도.

See

PersistenceException: Error loading on models.NotificationEvent.eventType

AbstractPostingApp의 inner interface였던 Notification을 models 패키지로 옮기자 이런 예외가 발생했다. controllers 패키지로 옮겼을 때는 아무런 문제가 없었다. models.support로 옮겨도 안됨.

play.api.Application$$anon$1: Execution exception[[PersistenceException: Error loading on models.NotificationEvent.eventType]]
        at play.api.Application$class.handleError(Application.scala:289) ~[play_2.10.jar:2.1.0]
        at play.api.DefaultApplication.handleError(Application.scala:383) ~[play_2.10.jar:2.1.0]
        at play.core.server.netty.PlayDefaultUpstreamHandler$$anon$2$$anonfun$handle$1.apply(PlayDefaultUpstreamHandler.scala:132) ~[play_2.10.jar:2.1.0]
        at play.core.server.netty.PlayDefaultUpstreamHandler$$anon$2$$anonfun$handle$1.apply(PlayDefaultUpstreamHandler.scala:128) ~[play_2.10.jar:2.1.0]
        at play.api.libs.concurrent.PlayPromise$$anonfun$extend1$1.apply(Promise.scala:117) ~[play_2.10.jar:2.1.0]
        at play.api.libs.concurrent.PlayPromise$$anonfun$extend1$1.apply(Promise.scala:117) ~[play_2.10.jar:2.1.0]
javax.persistence.PersistenceException: Error loading on models.NotificationEvent.eventType
        at com.avaje.ebeaninternal.server.query.SqlBeanLoad.load(SqlBeanLoad.java:113) ~[avaje-ebeanorm-server-3.1.2.jar:na]
        at com.avaje.ebeaninternal.server.deploy.BeanProperty.load(BeanProperty.java:600) ~[avaje-ebeanorm-server-3.1.2.jar:na]
        at com.avaje.ebeaninternal.server.query.SqlTreeNodeBean.load(SqlTreeNodeBean.java:257) ~[avaje-ebeanorm-server-3.1.2.jar:na]
        at com.avaje.ebeaninternal.server.query.SqlTreeNodeManyRoot.load(SqlTreeNodeManyRoot.java:29) ~[avaje-ebeanorm-server-3.1.2.jar:na]
        at com.avaje.ebeaninternal.server.query.SqlTreeNodeBean.load(SqlTreeNodeBean.java:289) ~[avaje-ebeanorm-server-3.1.2.jar:na]
        at com.avaje.ebeaninternal.server.query.CQuery.readRow(CQuery.java:526) ~[avaje-ebeanorm-server-3.1.2.jar:na]
Caused by: org.h2.jdbc.JdbcSQLException: Data conversion error converting "PULL_REQUEST_MERGED" [22018-168]
        at org.h2.message.DbException.getJdbcSQLException(DbException.java:329) ~[h2-1.3.168.jar:1.3.168]
        at org.h2.message.DbException.get(DbException.java:158) ~[h2-1.3.168.jar:1.3.168]
        at org.h2.value.Value.convertTo(Value.java:852) ~[h2-1.3.168.jar:1.3.168]
        at org.h2.value.Value.getInt(Value.java:417) ~[h2-1.3.168.jar:1.3.168]
        at org.h2.jdbc.JdbcResultSet.getInt(JdbcResultSet.java:307) ~[h2-1.3.168.jar:1.3.168]
        at com.avaje.ebeaninternal.server.type.RsetDataReader.getInt(RsetDataReader.java:119) ~[avaje-ebeanorm-server-3.1.2.jar:na]
Caused by: java.lang.NumberFormatException: For input string: "PULL_REQUEST_MERGED"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) ~[na:1.7.0_25]
        at java.lang.Integer.parseInt(Integer.java:492) ~[na:1.7.0_25]
        at java.lang.Integer.parseInt(Integer.java:527) ~[na:1.7.0_25]
        at org.h2.value.Value.convertTo(Value.java:809) ~[h2-1.3.168.jar:1.3.168]
        at org.h2.value.Value.getInt(Value.java:417) ~[h2-1.3.168.jar:1.3.168]
        at org.h2.jdbc.JdbcResultSet.getInt(JdbcResultSet.java:307) ~[h2-1.3.168.jar:1.3.168]

원인: NotificationEvent.eventType의 값들이 DB에는 문자열로 저장되어 있는데, 모델에서는 이 값을 숫자로 읽도록 되어 있어서(ordinal) 예외가 발생했다.

해결책:

  • enum을 persist하는 방법이 정확히 정의되지 않은 경우 발생할 수 있다. enum의 각 값마다 @EnumValue 를 이용해서 어떤 문자열로 저장되는지 명시해야한다. 그러지 않으면 임의로 동작하는 것으로 보이는
  • 같은 enum을 저장하는 방법이 그 enum을 사용하는 곳 마다 다른 경우(어디서는 ordinal, 어디서는 string) 어떤 규칙이 적용될 지 알 수 없다. hibernate는 이런 경우에도 잘 동작한다. ebean의 버그인듯.

exception when typing play.Project.javaCore

play 실행시 다음과 같은 에러가 발생하면,

exception when typing play.Project.javaCore
class file needed by PlayReloader is missing.
reference type SBTLink of package play.core refers to nonexisting symbol. in file /home/nori/mysrc/yobi/project/Build.scala
scala.tools.nsc.symtab.Types$TypeError: class file needed by PlayReloader is missing.
reference type SBTLink of package play.core refers to nonexisting symbol.

play 프레임워크의 repository/cache를 지우고 다시 해볼 것 (모두 다시 받아야 하므로 매우 시간이 많이 걸림)

idea에서 unittest 실행시 java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing

intellij idea에서 unittest를 실행하면 java.lang.NoClassDefFoundError: org/hamcrest/SelfDescribing 에러가 나는데 아무리해도 Project Structure에 추가가 안됨

java.lang.RuntimeException: Error reading annotations for models.User

models.User가 entity가 아닌 경우 발생

아무 이유 없이 테스트가 실행되지 않음

play clean 해본다.

[error] (*:update) sbt.ResolveException: unresolved dependency: com.typesafe.play#sbt-plugin;2.2-SNAPSHOT: not found

project/build.properties에서 sbt 버전을 현재 Play가 사용하는 버전으로 맞춘다.

java.lang.VerifyError: Stack map does not match the one at exception handler 353

intellij에서 catch block을 collapse 해주길래 그렇게 했더니만 이 에러가 난다.

play.api.Application$$anon$1: Execution exception[[RuntimeException: java.lang.VerifyError: Stack map does not match the one at exception handler 353
Exception Details:
Location:
    playRepository/GitRepository.cloneAndFetch(Lmodels/PullRequest;LplayRepository/GitRepository$AfterCloneAndFetchOperation;)V @353: astore_3
Reason:
    Type 'java/io/IOException' (current frame, stack[0]) is not assignable to 'org/eclipse/jgit/api/errors/GitAPIException' (stack map, stack[0])
Current Frame:
    bci: @2
    flags: { }
    locals: { 'models/PullRequest', 'playRepository/GitRepository$AfterCloneAndFetchOperation', null }
    stack: { 'java/io/IOException' }
Stackmap Frame:
    bci: @353
    flags: { }
    locals: { 'models/PullRequest', 'playRepository/GitRepository$AfterCloneAndFetchOperation', 'org/eclipse/jgit/lib/Repository' }
    stack: { 'org/eclipse/jgit/api/errors/GitAPIException' }

SeeAlso http://yobi.navercorp.com/dlab/hive/pullRequest/413#comment-2233

direct field access 시 null을 리턴함

thread.pullRequest.number 가 항상 null을 리턴하길래 확인해보니 스칼라 코드에서는 lazy loading이 동작하지 않는 모양이다:

(2) Enhancement of direct Ebean field access (enabling lazy loading) is only applied to Java classes, not to Scala. Thus, direct field access from Scala source files (including standard Play templates) does not invoke lazy loading, often resulting in empty (unpopulated) entity fields. To ensure the fields get populated, either (a) manually create getter/setters and call them instead, or (b) ensure the entity is fully populated before accessing the fields.

See http://www.playframework.com/documentation/2.2.x/JavaEbean

자바 코드에서도 그런 경우가 있는데 원인 불명. See http://yobi.navercorp.com/dlab/hive/commit/cb28895ef37214624873c3a6e197360d1d3d8604

존재하지 않는 column을 찾으려 함

다음과 같이 존재하지 않는 column을 조건으로 하는 이상한 쿼리를 실행하는 경우가 있다.

select t0.id c0
from n4user t0
where (issue_id) in (?)

Assignee를 삭제하려 하니 이런 문제가 발생했는데, issue에 대한 cascading을 제거하니 문제가 사라짐

sequence

ebean을 save할 때 id를 직접 지정하면 sequence가 증가하지 않는 것으로 보인다. 따라서 그렇게 한 경우 다음번 save에서 다음과 같은 에러를 유발할 수 있다.

[error] Test models.OrganizationTest.create failed: ERROR executing DML bindLog[] error[Unique index or primary key violation: "PRIMARY_KEY_D ON PUBLIC.ORGANIZATION(ID)"; SQL statement:\n insert into organization (id, name, cr
eated, descr) values (?,?,?,?) [23505-168]]
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:97)
[error]     at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.insert(DmlBeanPersister.java:57)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersistExecute.executeInsertBean(DefaultPersistExecute.java:72)
[error]     at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeNow(PersistRequestBean.java:481)
[error]     at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeOrQueue(PersistRequestBean.java:511)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.insert(DefaultPersister.java:387)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.saveEnhanced(DefaultPersister.java:326)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.saveRecurse(DefaultPersister.java:296)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.save(DefaultPersister.java:263)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.save(DefaultServer.java:1610)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.save(DefaultServer.java:1600)
[error]     at com.avaje.ebean.Ebean.save(Ebean.java:505)
[error]     at play.db.ebean.Model.save(Model.java:91)
[error]     at models.OrganizationTest.create(OrganizationTest.java:47)

이러한 이유 때문에 yaml로 데이터를 추가할 때 id를 지정하지 않는 것이 좋다.

view에서 이유를 알 수 없는 컴파일에러가 발생할 때

예:

[info] Compiling 2 Scala sources and 6 Java sources to /home/nori/mysrc/yobi/target/scala-2.10/classes...
[error] /home/nori/mysrc/yobi/app/views/git/viewChanges.scala.html:138: not enough arguments for method urlToCommentThread: (project: models.Project, thread: models.CommentThread)String.
[error] Unspecified value parameter thread.
[error]                                 <a href="@DiffRenderer.urlToCommentThread(thread)" class="review-card @thread.state.toString().toLowerCase()">
[error]                                                                          ^
[error] /home/nori/mysrc/yobi/app/views/reviewthread/partial_list.scala.html:41: not enough arguments for method urlToCommentThread: (project: models.Project, thread: models.CommentThread)String.
[error] Unspecified value parameter thread.
[error]                 <a href="@DiffRenderer.urlToCommentThread(thread)" class="title">
[error]                                                          ^
[error] two errors found
[error] (compile:compile) Compilation failed
[error] application -

해결책: play clean

@Transactional 안에서 ebean을 통해 entity를 가져왔을 때 property가 null이 되는 문제

원인

    @Transactional
    public Long increaseLastIssueNumber() {
        if (this.issues != null && lastIssueNumber < this.issues.size() ){ // 1
            fixLastIssueNumber();
        }
        lastIssueNumber++;
        update();
        return lastIssueNumber;
    }

    public void fixLastIssueNumber() {
        Issue issue = Issue.finder.where().eq("project.id", id).order().desc("number").findList().get(0); // 2
        lastIssueNumber = issue.number; // 3
    }

// 1에서 this.issues를 실행하면서 List 객체를 만들게 되는데 이때 해당 콜렉션에는 Issue의 id값만 채워져 있고 나머지 값들은 null이거나 primitive 타입의 기본값이 채워진 객체가 채워집니다. @xToMany의 fetch 속성이 기본으로 FetchType.LAZY이기 떄문입니다.

// 2에서 다시 이 프로젝트의 Issue 콜렉션을 findList()하지만 이때에 읽어온 데이터는 무시하는 일이 발생합니다. Ebean이 내부적으로 이미 Persistent Context에 동일한 객체가 있을 때는 DB에서 일거온 데이터를 무시하도록 코딩이 되어있습니다.

따라서 // 3에서 issue.number의 값(참고로 number의 타입은 Long)은 null이 나오고, 그 값을 lastIssueNumber(참고로 lastIssueNumber의 타입은 long)에 대입하려다 NullPointerException이 발생합니다.

해결책

트랜잭션 내에서 한번 읽어온 데이터를 다시 읽어와 사용할 필요가 있을 때에는 반드시 refresh()를 사용하는게 안전하다.

    public void fixLastIssueNumber() {
        Issue issue = Issue.finder.where().eq("project.id", id).order().desc("number").findList().get(0);
        issue.refresh();
        lastIssueNumber = issue.number;
    }

자세한 원인 (ebean 내부 분석)

다음과 같이 SqlBeanLoad.java:85 에서 sqlBeanLoad의 bean 멤버가 null이면 property를 불러오지 않는다.

	if ((bean == null) 
		|| (excludes != null && excludes.contains(prop.getName()))
		|| (type != null && !prop.isAssignableFrom(type))){

		// ignore this property 
		// ... null: bean already in persistence context
		// ... excludes: partial bean that is lazy loading
		// ... type: inheritance and not assignable to this instance
		
	    prop.loadIgnore(ctx);
		return null;
	}

sqlBeanLoad의 bean 멤버가 null이 된 것은, SqlTreeNodeBean.java이 context에 이미 그 bean이 있을 때는 새로 불러오지 않고 그냥 null로 두기 때문이다.

			// check the PersistenceContext to see if the bean already exists
			contextBean = persistenceContext.putIfAbsent(id, localBean);
			if (contextBean == null){
                // bean just added to the persistenceContext
                contextBean = localBean;                    				    
			} else {
				// bean already exists in persistenceContext
				if (queryMode.isLoadContextBean()){
					// refresh it anyway (lazy loading for example)
					localBean = contextBean;
					if (localBean instanceof EntityBean){
					    // temporarily turn off interception during load
					    ((EntityBean)localBean)._ebean_getIntercept().setIntercepting(false);
					}
				} else {
				    // ignore the DB data...
					localBean = null;	
				}
			}

	SqlBeanLoad sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);

위 코드에서 context에 bean이 있으므로, if 분기에 따라 localBean이 null이 되는데, 이게 나중에 sqlBeanLoad.bean이 된다. sqlBeanLoad는 다음과 같이 생성되며(SqlTreeNodeBean.java):

	SqlBeanLoad sqlBeanLoad = new SqlBeanLoad(ctx, localType, localBean, queryMode);

SqlbeanLoad의 생성자에서 넘겨받은 bean 매개변수로 멤버 bean을 설정하기 때문이다(SqlBeanLoad.java):

public SqlBeanLoad(DbReadContext ctx, Class<?> type, Object bean, Mode queryMode) {

	this.ctx = ctx;
	this.rawSql = ctx.isRawSql();
	this.type = type;
	this.isLazyLoad = queryMode.equals(Mode.LAZYLOAD_BEAN);
	this.bean = bean;
	
    if (bean instanceof EntityBean) {
        EntityBeanIntercept ebi = ((EntityBean) bean)._ebean_getIntercept();

        this.excludes = isLazyLoad ? ebi.getLoadedProps() : null;
        if (excludes != null) {
            // lazy loading a "Partial Object"... which already
            // contains some properties and perhaps some oldValues
            // and these will need to be maintained...
            originalOldValues = ebi.getOldValues();
        } else {
            originalOldValues = null;
        }
        this.setOriginalOldValues = originalOldValues != null;
    } else {
        this.excludes = null;
        this.originalOldValues = null;
        this.setOriginalOldValues = false;
    }
}

static 메소드에서 private field 업데이트 불가

예:

@Entity
public class Target extends Model {
    private int value;

    // 잘 동작함
    static public increaseValue() {
        value++;
        update();
    }

    // 안 동작함
    static public increaseValue(Target target) {
        target.value++;
        target.update();
    }
}

원인: 어째선지 static 메소드에서 private field에 접근할 때는 ebean enhancement가 동작하지 않는다. 따라서 필드의 값을 고쳐도 bean이 dirty상태가 되지 않으므로, update 메소드를 호출해도 DB에서 필드의 값이 갱신되지 않는다.

Play 2.3에서 Unable to create or alter sequence 에러

다음과 같은 에러가 발생하는 경우가 있다.

[error] play - Unable to create or alter sequence "POSTING_SEQ" because of invalid attributes (start value "0", min value "1", max value "9223372036854775807", increment "1"); SQL statement:

CREATE SEQUENCE posting_seq START WITH post_seq.currval [90009-175] [ERROR:90009, SQLSTATE:90009]

원인: Play 2.3에서 사용하는 h2database 1.3.175에 sequence를 생성하거나 수정할 수 없는 버그가 있기 때문이다.

해결책: h2database 1.3.176을 사용한다.

  1. 기존에 설치되어있던 h2database를 삭제한다.

     rm -r ~/.ivy2/cache/com.h2database/h2
    
  2. play-jdbc와 sbt-plugin이 h2database 1.3.175가 아닌 1.3.176을 의존하게 고친다.

    ~/.ivy2/cache/com.typesafe.play/play-jdbc_2.10/ivy-2.3.6.xml:

     <dependency org="com.h2database" name="h2" rev="1.3.176" force="true" conf="compile->compile(*),master(*);runtime->runtime(*)"/>
    

    ~/.ivy2/cache/scala_2.10/sbt_0.13/com.typesafe.play/sbt-plugin/ivy-2.3.6.xml:

     <dependency org="com.h2database" name="h2" rev="1.3.176" conf="compile->default(compile)"/>
    
  3. 다시 컴파일하면, h2database 1.3.176을 받아온다.

     activator compile
    
Clone this wiki locally