Java 使用Hibernate提交事务时的ConcurrentModificationException

Java 使用Hibernate提交事务时的ConcurrentModificationException,java,hibernate,Java,Hibernate,在我们的应用程序中,我们已经从Hibernate 3.5.6-final升级到4.2.21.final,现在我们在提交数据库事务时得到了一个ConcurrentModificationException: java.util.ConcurrentModificationException: null at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayLis

在我们的应用程序中,我们已经从Hibernate 3.5.6-final升级到4.2.21.final,现在我们在提交数据库事务时得到了一个
ConcurrentModificationException

java.util.ConcurrentModificationException: null
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:386)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:304)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:349)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:56)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1195)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:404)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)

这是Hibernate 4.2的一个已知问题吗?

这个异常是由我们使用的Hibernate自定义约束验证器的问题引起的。验证程序的
isValid
正在运行Hibernate条件查询。该查询触发了Hibernate会话刷新,导致出现
ConcurrentModificationException
。我们通过在
isValid
方法中临时禁用自动刷新来修复此问题:

@Override
public boolean isValid(Object object, final ConstraintValidatorContext c) {
   try {
      sessionFactory.getCurrentSession().setFlushMode(FlushMode.MANUAL);
      ...
   } finally {
      sessionFactory.getCurrentSession().setFlushMode(FlushMode.AUTO);
   }
}

这个问题也可能表现为一个
堆栈溢出错误

我在hibernate 5.0.11中遇到了这个问题,并在5.2.5中验证了它的存在。我的解决方案是注释自定义验证器以打开新事务

@Transactional(propagation=Propagation.REQUIRES_NEW)
我想在自定义约束验证器易于设置和使用之前,hibernate还有一段路要走,因为这花费了我更多的时间


编辑:与问题相关。我认为,在实现ConstraintValidator的类中,使用相同的事务违反了jpa2.1规范,我们需要EntityManager的实例,但我们不在Spring上下文中,以便使用注释@Autowired自动实例化EntityManager对象。因此,在配置包中,我们可以编写一个工厂,它允许有一个应用程序实例,以便在不在Spring上下文中时实例化bean

@Configuration
public class ApplicationContextConf {

    @Bean
    public static ApplicationContextProvider contextProvider() {
        return new ApplicationContextProvider();
    }

}



在实现ConstraintValidator的类中,由于前面在initialize方法中创建的工厂,我们实例化了EntityManager

在调用调用存储库的方法之前,我们将Hibernate当前会话的刷新模式更改为FlushMode.MANUAL,以避免在调用存储库后自动刷新,同时保持默认刷新模式。最后,在块中,我们恢复先前保留的刷新模式的默认值

private EntityManager entityManager;

@Override
public void initialize(final Object object ) {
    // ...

    try {
        this.entityManager = ApplicationContextConf
                .contextProvider()
                    .getApplicationContext()
                    .getBean(EntityManager.class);
    }
    catch (final BeansException ex) {
    // ...
    }
}

@Override
public boolean isValid(final Object object, final ConstraintValidatorContext context) {
    Session hibernateSession = null;
    FlushMode originalFlushMode = null;

    try {
        hibernateSession = this.entityManager.unwrap(Session.class);
        originalFlushMode = hibernateSession.getFlushMode();
        hibernateSession.setFlushMode(FlushMode.MANUAL);

        // ...
    }
    finally {
        if (hibernateSession != null) {
            hibernateSession.setFlushMode(originalFlushMode);
        }
    }
}

您是否使用实体事件侦听器进行了一些自定义工作(persit前或persist后),您需要将代码缩进(至少)4个空格以将其格式化。请在回答中添加一些解释。
private EntityManager entityManager;

@Override
public void initialize(final Object object ) {
    // ...

    try {
        this.entityManager = ApplicationContextConf
                .contextProvider()
                    .getApplicationContext()
                    .getBean(EntityManager.class);
    }
    catch (final BeansException ex) {
    // ...
    }
}

@Override
public boolean isValid(final Object object, final ConstraintValidatorContext context) {
    Session hibernateSession = null;
    FlushMode originalFlushMode = null;

    try {
        hibernateSession = this.entityManager.unwrap(Session.class);
        originalFlushMode = hibernateSession.getFlushMode();
        hibernateSession.setFlushMode(FlushMode.MANUAL);

        // ...
    }
    finally {
        if (hibernateSession != null) {
            hibernateSession.setFlushMode(originalFlushMode);
        }
    }
}