JPAValidateListener checks @NotNull fields that are filled after the @PrePersist call

Description

According to Hibernate validation documentation ( http://www.hibernate.org/hib_docs/validator/reference/en/html_single/#validator-checkconstraints-orm-jpaevent ) we can annotate an entity with @EntityListeners({JPAValidateListener.class}) to validate the fields using the @PrePersist / @PreUpdate JPA events handlers.

I tryied with the following entity:

@Entity
@Table(...)
@EntityListeners({JPAValidateListener.class})
public class A {
private int id;
private String text;

@Id
@Column(...)
@NotNull
@GeneratedValue(...)
@GenericGenerator(...)
public int getId() { return id; }
public void setId(int id) { this.id=id; }
...
}

Problem is that the @PrePersist event is raised before some fields are automatically filled (e.g. @Id annotated). So the JPAValidateListener raises an exception saying that the identifier cannot be null.

The exception root cause detail is (InvalidState: id cannot be null) and the stacktrace is:
org.hibernate.validator.InvalidStateException: validation failed for : my.package.A
at org.hibernate.validator.ClassValidator.assertValid(ClassValidator.java:666)
at org.hibernate.validator.event.JPAValidateListener.onChange(JPAValidateListener.java:62)
...// some reflection StacktraceElement
at org.hibernate.ejb.event.ListenerCallback.invoke(ListenerCallback.java:31)
at org.hibernate.ejb.event.EntityCallbackHandler.callback(EntityCallbackHandler.java:80)
at org.hibernate.ejb.event.EntityCallbackHandler.preCreate(EntityCallbackHandler.java:49)
at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:48)
at org.hibernate.event.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
at org.hibernate.event.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
at org.hibernate.event.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619)
at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623)
...

The EJB3PersistEventListener.saveWithGeneratedId() method looks like:

protected Serializable saveWithGeneratedId(...) {
callbackHandler.preCreate(entity); // raises the @PrePersist JPA event
return super.saveWithGeneratedId(...); // sets the identifier
}

Thus, the JPAValidateListener validates the whole Entity and does not take the entity lifecycle into account. The JPAValidateListener should check all fields except the ones which are known to be modified/setled after the call to the validator.

This may also be the case for other generated fields such as the ones annotated with @Version (I did not check that).

Environment

Hibernate 3.3.1.GA, Hibernate annotations 3.4.0.GA, Hibernate entity manager 3.4.0.GA, POJO annotated entities, DB2

Activity

Show:
Julien Kronegg
April 6, 2009, 3:09 PM

The same problem occurs when using an OpenJPA persistence provider version 1.2.1. In this case, the stacktrace is:

org.hibernate.validator.InvalidStateException: validation failed for : my.package.A
at org.hibernate.validator.ClassValidator.assertValid(ClassValidator.java:666)
at org.hibernate.validator.event.JPAValidateListener.onChange(JPAValidateListener.java:62)
...// some reflection StacktraceElement
at org.apache.openjpa.event.BeanLifecycleCallbacks.makeCallback(BeanLifecycleCallbacks.java:85)
at org.apache.openjpa.event.LifecycleEventManager.makeCallbacks(LifecycleEventManager.java:340)
at org.apache.openjpa.event.LifecycleEventManager.fireEvent(LifecycleEventManager.java:302)
at org.apache.openjpa.kernel.fireLifecycleEvent(BrokerImpl.java:688)
at org.apache.openjpa.kernel.persist(BrokerImpl.java:2428)
... // cutted down some OpenJPA StacktraceElements
at org.apache.openjpa.persistence.EntityManagerImpl.persist(EntityManagerImpl.java:645)

The BrokerImpl.persist (around line 2428) is:

fireLifecycleEvent(..., LifecycleEvent.BEFORE_PERSIST); // raises the @PrePersist JPA event
if (id==null) {
... // sets the identifier
}

Consequently, the issue is the same for both Hibernate and OpenJPA persistence providers.
AFAIK, the JPA specification does not give guidelines on when must be @PrePersist event raised. Thus, both providers implementation may be considered as correct, which confirms that the issue should be in the Hibernate Validator.

Julien Kronegg
April 6, 2009, 5:26 PM

Workaround:
I wrote my own JPAValidateListener from the original one with these changes:

  • validator.assertValid( object );
    + InvalidValue[] ivs = validator.getInvalidValues(object);

Then I check the InvalidValue field name to determine by reflection if the field comes from a property annotated with @Id.

Since the workaround is easy to do, the issue priority can be decreased.

Assignee

Unassigned

Reporter

Julien Kronegg

Labels

None

Feedback Requested

None

Feedback Requested By

None

backPortable

None

Suitable for new contributors

None

Pull Request

None

backportDecision

None

backportReEvaluate

None

Time tracking

16h

Priority

Major
Configure