使用hibernate验证时,Hibernate会话会被刷新

时间:2012-12-08 07:44:09

标签: hibernate bean-validation spring-transactions

我在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()具有当前正在验证的实体。在插入之前是否应该出现在这里?

3 个答案:

答案 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规范相反,但我确信此验证器提取的数据是主数据的一部分,不会导致像幻像读取这样的隔离问题。