1

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

4

3 回答 3

2

JPA 规范说:

通常,可移植应用程序的生命周期方法不应调用 EntityManager 或 Query 操作、访问其他实体实例或修改同一持久性上下文中的关系。

它还说:

使用这些约束的自动验证是通过在第 3.5.2 节中描述的 pre-persist、pre-update 和 pre-remove 实体生命周期事件时指定 Java Persistence 将验证委托给 Bean Validation 实现来实现的

因此,规范明确表示您不应该在生命周期事件中使用实体管理器,并且使用生命周期事件执行自动验证。因此,您不能在验证器中使用实体管理器。

于 2012-12-08T13:18:52.187 回答
0

如上一个答案中所指出的,在生命周期事件期间不应执行任何 EntityManager 操作。如果你真的想这样做,你应该打开一个 tmp 会话。另请参阅https://community.jboss.org/wiki/AccessingTheHibernateSessionWithinAConstraintValidator

于 2012-12-09T15:39:39.020 回答
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 规范,但我确信这个验证器获取的数据是主数据的一部分,不会导致像幻读这样的隔离问题。

于 2012-12-30T14:06:56.930 回答