-->

Play Framework nested form errors missing

2019-07-16 05:09发布

问题:

I am using Play Framework 2.3 with Java and I have the following one-to-many relationship between a committee and its recorded meeting dates:

@Entity
public class Committee {
    @Id
    protected Long id;

    @OneToMany(cascade=CascadeType.ALL, mappedBy="committee", orphanRemoval=true)
    private List<MeetingDate> meetingDates;
}

@Entity
public class MeetingDate {
    @Id
    private Long id;

    @Constraints.Required
    @JoinColumn(nullable=false)
    @ManyToOne
    private Committee committee;

    @Constraints.Required
    @Formats.DateTime(pattern="dd MMM yyyy")
    @Temporal(TemporalType.DATE)
    @Column(nullable=false)
    private Date meetingDate;
}

I have a form that allows the user to edit a committee and the associated meeting dates on the one page. I've followed the "Forms" sample application from https://www.playframework.com/documentation/2.2.x/Samples and my template code looks like this:

@meetingDateGroup(field: Field, className: String = "meeting-date") = {
    <tr class="repeating-group @className">
      <td>
        <input type="hidden" id='@field("id").name' name='@field("id").name' value='@field("id").value'>
        <input type="text" id='@field("meetingDate").name' name='@field("meetingDate").name' value='@field("meetingDate").value' class="form-control">
        @if(field("meetingDate").hasErrors){
            <div class="help-block"><span class="glyphicon glyphicon-exclamation-sign"></span> @field("meetingDate").errors.mkString(", ")</div>
        }
      </td>
      <td align="right"><a href="javascript:void(0)" class="remove-meeting-date rowIcon"><span class="glyphicon glyphicon-remove"></span> Delete</a></td>
    </tr>
}

...

@helper.repeat(form("meetingDates"), min=1) { meetingDate =>
    @meetingDateGroup(meetingDate)
}

@**
 * Keep a hidden block that will be used as template for Javascript copy code
 **@
@meetingDateGroup(
    form("meetingDates[x]"), className = "hidden meeting-date-template"
)

Note that there is extra Javascript code (not shown) to add and remove the meeting date rows from the table dynamically. This Javascript code was copied from the Forms sample application.

The problem I have is that validation is not performed on the MeetingDate entities. For example, I add an extra meeting date row and don't enter a value for the date. When I save the committee, I don't get any errors in the Form, but the exception below occurs:

play.api.Application$$anon$1: Execution exception[[RollbackException: Error while committing the transaction]]
        at play.api.Application$class.handleError(Application.scala:296) ~[play_2.11-2.3.4.jar:2.3.4]
        at play.api.DefaultApplication.handleError(Application.scala:402) [play_2.11-2.3.4.jar:2.3.4]
        at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$3$$anonfun
$applyOrElse$4.apply(PlayDefaultUpstreamHandler.scala:320) [play_2.11-2.3.4.jar:2.3.4]
        at play.core.server.netty.PlayDefaultUpstreamHandler$$anonfun$3$$anonfun
$applyOrElse$4.apply(PlayDefaultUpstreamHandler.scala:320) [play_2.11-2.3.4.jar:2.3.4]
        at scala.Option.map(Option.scala:145) [scala-library-2.11.1.jar:na]
Caused by: javax.persistence.RollbackException: Error while committing the transaction
        at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:94) ~[hibernate-entitymanager-4.3.5.Final.jar:4.3.5.Final]
        at play.db.jpa.JPA$2.apply(JPA.java:193) ~[play-java-jpa_2.11-2.3.4.jar:2.3.4]
        at play.core.j.FPromiseHelper$$anonfun$map$1.apply(FPromiseHelper.scala:98) ~[play_2.11-2.3.4.jar:2.3.4]
        at scala.util.Success$$anonfun$map$1.apply(Try.scala:236) ~[scala-library-2.11.1.jar:na]
        at scala.util.Try$.apply(Try.scala:191) ~[scala-library-2.11.1.jar:na]
Caused by: javax.validation.ConstraintViolationException: Validation failed forclasses [models.MeetingDate] during persist time for groups [javax.validation.groups.Default, ]
List of constraint violations:[
        ConstraintViolationImpl{interpolatedMessage='error.required', propertyPath=meetingDate, rootBeanClass=class models.MeetingDate, messageTemplate='error.required'}
]
        at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.validate(BeanValidationEventListener.java:160) ~[hibernate-core-4.3.6.Final.jar:4.3.6.Final]
        at org.hibernate.cfg.beanvalidation.BeanValidationEventListener.onPreInsert(BeanValidationEventListener.java:95) ~[hibernate-core-4.3.6.Final.jar:4.3.6.Final]
        at org.hibernate.action.internal.EntityInsertAction.preInsert(EntityInsertAction.java:218) ~[hibernate-core-4.3.6.Final.jar:4.3.6.Final]
        at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:97) ~[hibernate-core-4.3.6.Final.jar:4.3.6.Final]
        at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463) ~[hibernate-core-4.3.6.Final.jar:4.3.6.Final]
[ERROR] [09/03/2014 23:25:59.167] [play-akka.actor.default-dispatcher-4] [akka.dispatch.Dispatcher] EntityManager is closed
java.lang.IllegalStateException: EntityManager is closed
        at org.hibernate.jpa.internal.EntityManagerImpl.checkOpen(EntityManagerImpl.java:105)
        at org.hibernate.jpa.internal.EntityManagerImpl.checkOpen(EntityManagerImpl.java:96)
        at org.hibernate.jpa.internal.EntityManagerImpl.close(EntityManagerImpl.java:148)
        at play.db.jpa.JPA$3.invoke(JPA.java:209)
        at play.db.jpa.JPA$3.invoke(JPA.java:203)
        at play.core.j.FPromiseHelper$$anonfun$onFailure$1.applyOrElse(FPromiseHelper.scala:116)
        at play.core.j.FPromiseHelper$$anonfun$onFailure$1.applyOrElse(FPromiseHelper.scala:116)
        at scala.concurrent.Future$$anonfun$onFailure$1.apply(Future.scala:136)
        at scala.concurrent.Future$$anonfun$onFailure$1.apply(Future.scala:134)
        at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
        at play.core.j.HttpExecutionContext$$anon$2.run(HttpExecutionContext.scala:40)
        at akka.dispatch.TaskInvocation.run(AbstractDispatcher.scala:41)
        at akka.dispatch.ForkJoinExecutorConfigurator$AkkaForkJoinTask.exec(AbstractDispatcher.scala:393)
        at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
        at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
        at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
        at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

[error] a.d.Dispatcher - EntityManager is closed
java.lang.IllegalStateException: EntityManager is closed
        at org.hibernate.jpa.internal.EntityManagerImpl.checkOpen(EntityManagerImpl.java:105) ~[hibernate-entitymanager-4.3.5.Final.jar:4.3.5.Final]
        at org.hibernate.jpa.internal.EntityManagerImpl.checkOpen(EntityManagerImpl.java:96) ~[hibernate-entitymanager-4.3.5.Final.jar:4.3.5.Final]
        at org.hibernate.jpa.internal.EntityManagerImpl.close(EntityManagerImpl.java:148) ~[hibernate-entitymanager-4.3.5.Final.jar:4.3.5.Final]
        at play.db.jpa.JPA$3.invoke(JPA.java:209) ~[play-java-jpa_2.11-2.3.4.jar:2.3.4]
        at play.db.jpa.JPA$3.invoke(JPA.java:203) ~[play-java-jpa_2.11-2.3.4.jar:2.3.4]

The MeetingDate object is definitely invalid because it's meetingDate property is null. My question is: why is this error not reported in the form errors, i.e. why is the following expression false?

Form.form(Committee.class).bindFromRequest().hasErrors()

回答1:

Just figured out what I was missing - I needed to add the @Valid annotation to my one-to-many relationship:

@Valid
@OneToMany(cascade=CascadeType.ALL, mappedBy="committee", orphanRemoval=true)
private List<MeetingDate> meetingDates;

This triggers the validation on the nested field.