如何解决多线程 Hibernate 从 4.x 升级到 5.x 和 Spring 4.x 时的 ConcurrentModificationException
当我们从 Hibernate 4 迁移到 5 时,我遇到了问题。 我从一个星期开始一直在挣扎,阅读了无数的博客和页面,但无法解决。 以下是有关该问题的更多信息:-
1) 休眠版本: 来自:4.3.11.Final 至:5.4.28.Final
2) Spring ORM 版本: 4.3.29.RELEASE
3) Spring Batch 基础设施/核心: 3.0.10.Release
4) Hibernate 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 Batch 配置
<?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;
}
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
- Hibernate5.HibernateTransactionManager
如果我恢复上述 Hibernate4 的更改,则代码在多线程池中运行良好,但在上述更新时抛出 ConcurrentModificationException。
我确定我一定遗漏了一些非常愚蠢的东西或一些需要作为 Hibernate 5 迁移的一部分添加的设置。
非常感谢任何建议。
提前致谢。
解决方法
Hibernate 会话和从该会话加载的对象总的来说不是线程安全的。所以你不能从一个会话中加载对象,然后在不同的线程中使用这些对象。
除其他问题外,这些对象可能会触发惰性集合/代理的初始化,这最终落在底层 JDBC 连接上,并且该连接也不是线程安全的。
你需要给每个线程它自己的会话,并且你不能在线程之间共享从会话加载的对象
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。