Niel de Wet
Niel de Wet

Reputation: 8408

With Hibernate validation, can I query the entity being validated?

I have an entity on which I need to implement the following constraint: "There may only ever be one record for any combination of columns X and Y, for which column Z is null if the record is of type A."

The last part turns this from a simple uniqueness constraint to something more complex. I'm writing a custom Hibernate validator to check it.

What I'm doing is:

@Override
public boolean isValid(MyEntity value, ConstraintValidatorContext context) {
    Query query = DB.createQuery( // DB is just a convenience class
                "select count(*) from MyEntity" +
                "  where propertyX = :propertyX" +
                "    and propertyY = :propertyY" +
                "    and type = :type" +
                "    and propertyZ is null")
                .setParameter("propertyX", value.getPropertyZ())
                .setParameter("propertyY", value.getPropertyY())
                .setParameter("type", MyType.PRIMARY);

     return query.getResultList().size() <= 1;
}

If there is more than one such record the validation should fail. This will enforce always setting propertyZ before inserting a new entry.

However this does not work because this validation happens onPersist and at that point the query returns a result with a null id, which causes an exception.

Here are some interesting lines from the stack trace:

[junit] org.hibernate.AssertionFailure: null id in my.package.MyEntity entry (dont flush the Session after an exception occurs)
[junit]     at org.hibernate.event.def.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:82)
[junit]     at org.hibernate.event.def.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:190)
[junit]     at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:147)
[junit]     at org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219)
[junit]     at org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
[junit]     at org.hibernate.event.def.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
[junit]     at org.hibernate.impl.SessionImpl.autoFlushIfRequired(SessionImpl.java:1185)
[junit]     at org.hibernate.impl.SessionImpl.list(SessionImpl.java:1261)
[junit]     at org.hibernate.impl.QueryImpl.list(QueryImpl.java:102)
[junit]     at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:246)
[junit]     at my.package.validation.UniqueCombinationTypeValidator.isValid(UniqueCombinationTypeValidator.java:42)
[junit]     at my.package.validation.UniqueCombinationTypeValidator.isValid(UniqueCombinationTypeValidator.java:14)
[junit]     at org.hibernate.validator.engine.ConstraintTree.validateSingleConstraint(ConstraintTree.java:153)
[junit]     at org.hibernate.validator.engine.ConstraintTree.validateConstraints(ConstraintTree.java:140)
...
[junit]     at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
[junit]     at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:808)
[junit]     at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:782)
[junit]     at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:786)
[junit]     at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:672)
[junit]     at my.package.DB.persist(DB.java:278)
[junit]     at my.package.test.model.validation.UniqueCombinationTypeValidatorTest.testInsert(UniqueCombinationTypeValidatorTest.java:72)

One other thing to note is that the table is empty when this first insert is done.

THE QUESTION IS, can I query the same table that I am trying to validate? It seems like a very logical requirement, since a constraint annotation can be put on the class. My validation is dependent on the state of the data.

Upvotes: 3

Views: 2397

Answers (1)

Niel de Wet
Niel de Wet

Reputation: 8408

Inspired by this post and the knowledge that it is never OK to call/use the same session in any callback methods that are triggered from the session, I managed to solve the problem quite simply by getting hold of my EntityManagerFactory, which in turn lets me get an EntityManager, which gives me a new Session. Now I can use this to do the query.

EntityManager em = emFactory.createEntityManager();
session = (Session) em.getDelegate();
Query query = session.createQuery(...

NOTE: That from the EntityManager docs, getDelegate() is implementation specific. I'm using tomcat and hibernate, and it work's well.

Upvotes: 4

Related Questions