当hibernate从4.x升级到5.x和多线程的Spring4.x时,concurrentmodificationexception

trnvg8h3  于 2021-07-13  发布在  Java
关注(0)|答案(1)|浏览(394)

当我们从Hibernate4迁移到5时,我遇到了一个问题。我已经挣扎了一个星期,阅读了大量的博客和网页,但无法解决。以下是关于issue:-
1) 休眠版本:从:4.3.11.final到:5.4.28.final
2) spring orm版本:4.3.29.release
3) spring批处理基础设施/核心:3.0.10.release
4) 休眠xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<bean id="sessionFactory"
      class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">

    <property name="dataSource">
        <ref bean="dataSource"/> <!-- set in another xml using org.apache.commons.dbcp.BasicDataSource --> 
    </property>

    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.id.new_generator_mappings">true</prop>
            <!--  -->
            <prop key="hibernate.jdbc.batch_size">1000</prop>
            <prop key="hibernate.jdbc.fetch_size">1000</prop>
            <prop key="hibernate.order_inserts">true</prop>
            <prop key="hibernate.order_updates">true</prop>
        </props>
    </property>

    <property name="annotatedClasses">
        <list>
            <value>com.company.ParentTable</value>
            <value>com.company.ChildTable</value>
        </list>
    </property>
</bean>

<bean id="transactionTemplate"
      class="org.springframework.transaction.support.TransactionTemplate"
      p:isolationLevelName="ISOLATION_READ_COMMITTED"
      p:propagationBehaviorName="PROPAGATION_REQUIRES_NEW"
      p:transactionManager-ref="transactionManager"
/>

<tx:annotation-driven transaction-manager="transactionManager"/>

<!-- Spring transaction management -->
<bean id="transactionManager"
      class="org.springframework.orm.hibernate5.HibernateTransactionManager"
      p:sessionFactory-ref="sessionFactory"
/>

5) Spring批配置

<?xml version="1.0" encoding="UTF-8"?>

<batch:job id="loaderJob" job-repository="jobRepository">
    <batch:step id="initLoader" next="pipeline">
        <batch:tasklet transaction-manager="transactionManager">
            <bean class="com.company.InitialLoaderTask" scope="step">
                <constructor-arg name="dao" ref="dao"/>
                <constructor-arg name="filter" value="#{jobParameters['filter']}"/>
            </bean>
        </batch:tasklet>
    </batch:step>
    <batch:step id="pipeline">
        <batch:tasklet transaction-manager="transactionManager" task-executor="loader_multi_thread_taskExecutor" throttle-limit="50">
            <batch:chunk
                    reader="reader"
                    writer="processorAndWriter"
                    commit-interval="1000">
        </batch:tasklet>
    </batch:step>
</batch:job>

<bean id="reader" class="com.company.SynchronizedItemStreamReader" scope="step" > <!-- Custom Synchrnized Item Reader --> 
    <constructor-arg name="delegate" ref="#{ jobExecutionContext['region'] ? 'hibernateReader':'hibernateReader2' }"/> <!-- hibernateReader2 is same as hibernateReader but with more params -->
</bean>

<bean id="hibernateReader"
      class="org.springframework.batch.item.database.HibernateCursorItemReader"
      scope="step" >
    <property name="sessionFactory" ref="sessionFactory"/>
    <property name="queryName" value="ParentTable.query1"/>
    <property name="useStatelessSession" value="false"/>
    <property name="saveState" value="false"/>
    <property name="fetchSize" value="10000"/>
    <property name="parameterValues">
        <map>
            <entry key="param1" value="#{jobExecutionContext['param1']}"/>
            <entry key="param2" value="#{jobExecutionContext['param2']}"/>
        </map>
    </property>
</bean>

<bean id="processorAndWriter"
      class="com.company.LoaderAndWriter"
      >
    <constructor-arg name="generator" ref="processor"/>
    <constructor-arg name="writer" ref="hibernateWriter"/>
</bean>

<bean id="processor" class="org.springframework.batch.item.support.CompositeItemProcessor" >
    <property name="delegates">
        <list>
            <bean class="com.company.Generator" scope="step">
                <constructor-arg name="param1" value="#{jobExecutionContext['param1']}"/>
            </bean>
            <bean class="com.company.Processor" scope="step">
                <constructor-arg name="param3" value="#{jobExecutionContext['param3']}"/>
            </bean>
        </list>
    </property>
</bean>

<bean id="hibernateWriter" class="org.springframework.batch.item.database.HibernateItemWriter">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<bean id="loader_multi_thread_taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
    <property name="corePoolSize" value="25"/>
    <property name="maxPoolSize" value="50"/>
</bean>

6) 实体对象

@Entity
@Table(name = "PARENT_TABLE")
@NamedNativeQueries({
        @NamedNativeQuery(
                name = "ParentTable.query1",
                query = "SELECT * FROM PARENT_TABLE where param = :param1",
                resultClass = ParentTable.class
        )
})
@BatchSize(size = 1000)
public class ParentTable extends PersistentEntity<Long> {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PARENT_TABLE_SEQ")
    @SequenceGenerator(name = "PARENT_TABLE_SEQ", sequenceName = "PARENT_TABLE_SEQ", allocationSize=5)
    private Long id;

    @Column
    private Long field1;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "fieldId", fetch = FetchType.EAGER)
    @BatchSize(size = 1000)
    private List<ChildTable> childTable;
}

@Entity
@Table(name = "CHILD_TABLE")
@BatchSize(size = 1000)
public class ChildTable extends PersistentEntity<Long> {
    private static final long serialVersionUID = 1L;

    @Id
    private Long id;

    @ManyToOne
    @JoinColumn(name = "field1")
    private ParentTable parentData;

    @Column
    private String field2;
}

7) 从parentdata列表访问子表数据时引发错误的生成器类

public class Generator implements ItemProcessor<List<? extends ParentTable>, RequestPojoBean> {
private final int param1;
public Generator(int param1) {
        this.param1 = param1;
}
@Override
public RequestPojoBean process(List<? extends ParentTable> parentDataList) throws Exception {
    RequestPojoBean result = new RequestPojoBean();
    result.setParentDataList(convertToRequestList(parentDataList));
    result.setparam1(param1);
    return result;
}
 private List<ParentRequestPojoBean> convertToRequestList(List<? extends ParentTable> parentDataList) {
    List<ParentRequestPojoBean> parentRequestBean = new ArrayList<>();
    for (ParentTable parentData : parentDataList) {
        LOGGER.info("Parent detail " + parentData.getField1());
        ParentRequestPojoBean bean1 = new ParentRequestPojoBean();
        bean1.setSomeData(callToSomeServiceViaHibernate(parentData.somedata, param1));

        // attach References if any
        List<RequestPojoBeanChildData> childList = new ArrayList<>();
        for (ChildTable child : parentData.getChildTable()) {  *****<--- Exception is thrown at this line*****
            RequestPojoBeanChildData childPojoData = new RequestPojoBeanChildData();
            childPojoData.setField2(child.getField2());
            childList.add(childPojoData);
        }
        bean1.setChildData(childList);
        parentRequestBean.add(bean1);
    }
    return parentRequestBean;
}
}

8) 异常堆栈跟踪:

2021-04-12 13:33:42,548 ERROR [main] [org.springframework.batch.core.step.AbstractStep] - <Encountered an error executing step pipeline in job loaderJob>
java.util.ConcurrentModificationException
    at java.util.LinkedHashMap$LinkedHashIterator.nextNode(LinkedHashMap.java:719)
    at java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:752)
    at java.util.LinkedHashMap$LinkedEntryIterator.next(LinkedHashMap.java:750)
    at org.hibernate.engine.spi.BatchFetchQueue.getCollectionBatch(BatchFetchQueue.java:311)
    at org.hibernate.loader.collection.plan.LegacyBatchingCollectionInitializerBuilder$LegacyBatchingCollectionInitializer.initialize(LegacyBatchingCollectionInitializerBuilder.java:79)
    at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:710)
    at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:76)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:93)
    at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:2163)
    at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:589)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
    at org.hibernate.collection.internal.PersistentBag.iterator(PersistentBag.java:387)

作为迁移的一部分,我更新了
休眠版本
hibernate5.localsessionfactorybean
休眠5.hibernatetransactionmanager
如果我为hibernate4恢复上面提到的更改,代码在多线程池中运行良好,但会抛出带有上述更新的concurrentmodificationexception。
我肯定我错过了一些非常愚蠢的东西,或者一些需要作为Hibernate5迁移的一部分添加的设置。
任何建议都将不胜感激。
提前谢谢。

wwtsj6pe

wwtsj6pe1#

hibernate会话和从该会话加载的对象一起不是线程安全的。因此,不能从会话加载对象,然后在不同的线程中使用这些对象。
在其他问题中,这些对象可能会触发延迟集合/代理的初始化,这最终会落在底层jdbc连接上,并且该连接也不是线程安全的。
您需要给每个线程分配它自己的会话,并且不能在线程之间共享从会话加载的对象

相关问题