Reputation: 569
I am using JSR-303 validation in hibernate. I have created a custom validator annotation which checks for data integrity by querying the database.
For example:
public boolean isValid(Object value, ConstraintValidatorContext context) {
if(value == null){
return true;
}
//This method uses entityManager to fetch a list from database
Map<String, String> pickList = pickListProvider.getPickList(picklistName);
return pickList.contains(value);
}
Usage:
public class UserProfile extends Persistent {
//Member fields come here
@PicklistConstraint(name="languages")
private String prefLanguage;
}
Here UserProfile is an entity persisted using Hibernate. Hibernate calls this validation at pre insert or pre update. However when the validator tries to fetch records from the database, hibernate is flushing the session. Since the domain object on which validation runs is not fully baked (doesn't have Id field), I get the following exception
org.hibernate.AssertionFailure: null id in <domain object here> entry (don't flush the Session after an exception occurs)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.checkId(DefaultFlushEntityEventListener.java:79)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.getValues(DefaultFlushEntityEventListener.java:194)
at org.hibernate.event.internal.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:156)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:225)
at org.hibernate.event.internal.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
at org.hibernate.event.internal.DefaultAutoFlushEventListener.onAutoFlush(DefaultAutoFlushEventListener.java:58)
at org.hibernate.internal.SessionImpl.autoFlushIfRequired(SessionImpl.java:1186)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1241)
at org.hibernate.internal.QueryImpl.list(QueryImpl.java:101)
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:257)
Upon investigation I found that flushMightBeNeeded of DefaultAutoFlushEventListener returns true:
private boolean flushMightBeNeeded(final EventSource source) {
return !source.getFlushMode().lessThan(FlushMode.AUTO) &&
source.getDontFlushFromFind() == 0 &&
( source.getPersistenceContext().getEntityEntries().size() > 0 ||
source.getPersistenceContext().getCollectionEntries().size() > 0 );
}
The issue is that FlushMode is AUTO and source.getPersistenceContext().getEntityEntries().size() has some entries. What should be my approach in such a scenario? Should I change the FlushMode from AUTO to MANUAL? Is it possible to get an new session or persistent context, different from the actual entity? I am using spring mvc along with hibernate.
Edit
I found that source.getPersistenceContext().getEntityEntries() has the entity which is currently being validated. Should that be present here before getting inserted?
Upvotes: 1
Views: 2116
Reputation: 569
I have added the following code in my validator and seems to work fine:
public boolean isValid(Object value, ConstraintValidatorContext context) {
if(value == null){
return true;
}
//This method uses entityManager to fetch a list from database
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Map<String, String> pickList = (Map<String, String>) txTemplate.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
return pickListProvider.getPickList(picklistName);
}
});
return pickList.contains(value);
}
I understand that this seems to be contrary to JPA spec, but I'm sure that the data being fetched by this validator is part of the master data and will not lead to isolation issues like phantom reads.
Upvotes: 0
Reputation: 19119
As pointed out in the previous answer no EntityManager operations should be executed during life cycle events. If you really want to do it you should open a tmp session. See also https://community.jboss.org/wiki/AccessingTheHibernateSessionWithinAConstraintValidator
Upvotes: 0
Reputation: 691755
The JPA spec says:
In general, the lifecycle method of a portable application should not invoke EntityManager or Query operations, access other entity instances, or modify relationships within the same persistence context.
And it also says:
Automatic validation using these constraints is achieved by specifying that Java Persistence delegate validation to the Bean Validation implementation upon the pre-persist, pre-update, and pre-remove entity lifecycle events described in Section 3.5.2
So the spec explicitely says that you shouldn't use the entity manager in a life-cycle event, and that automatic validation is performed using life-cycle events. So, you can't use the entity manager in a validator.
Upvotes: 2