我在hibernate中使用JSR-303验证。我创建了一个自定义验证器注释,它通过查询数据库来检查数据完整性。
例如:
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);
}
用法:
public class UserProfile extends Persistent {
//Member fields come here
@PicklistConstraint(name="languages")
private String prefLanguage;
}
这里UserProfile是一个使用Hibernate持久化的实体。 Hibernate在插入前或更新前调用此验证。但是当验证程序尝试从数据库中获取记录时,hibernate正在刷新会话。由于运行验证的域对象未完全烘焙(没有Id字段),因此我得到以下异常
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)
经过调查,我发现DefaultAutoFlushEventListener的flushMightBeNeeded返回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 );
}
问题是FlushMode是AUTO而source.getPersistenceContext()。getEntityEntries()。size()有一些条目。 在这种情况下我的方法应该是什么?我应该将FlushMode从AUTO更改为MANUAL吗?是否有可能获得与实际实体不同的新会话或持久上下文? 我正在使用spring mvc和hibernate。
修改
我发现source.getPersistenceContext()。getEntityEntries()具有当前正在验证的实体。在插入之前是否应该出现在这里?
答案 0 :(得分:2)
JPA规范说:
通常,可移植应用程序的生命周期方法不应调用EntityManager 或查询操作,访问其他实体实例或修改其中的关系 相同的持久性背景。
它还说:
使用这些约束的自动验证是通过指定Java Persistence委托来实现的 在预先保留,预更新和预删除之前验证Bean Validation实现 第3.5.2节中描述的实体生命周期事件
因此,规范明确表示您不应在生命周期事件中使用实体管理器,并且使用生命周期事件执行自动验证。因此,您无法在验证器中使用实体管理器。
答案 1 :(得分:0)
正如前面的回答所指出的那样,在生命周期事件中不应该执行EntityManager操作。如果你真的想这样做,你应该打开一个tmp会话。另请参阅https://community.jboss.org/wiki/AccessingTheHibernateSessionWithinAConstraintValidator
答案 2 :(得分:0)
我在验证器中添加了以下代码,似乎工作正常:
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);
}
我知道这似乎与JPA规范相反,但我确信此验证器提取的数据是主数据的一部分,不会导致像幻像读取这样的隔离问题。