Reputation: 8408
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
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