I am having an issue when we migrated from Hibernate 4 to 5. I have been struggling from a week and read numerous blogs and pages, but could not resolve. Following are more information on the issue:-
1) Hibernate Version : From: 4.3.11.Final To : 5.4.28.Final
2) Spring ORM version : 4.3.29.RELEASE
3) Spring Batch Infrastructre/Core: 3.0.10.Release
4) Hibernate XML Configuration :
<?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 Configuration
<?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 Objects
@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) Generator Class where error is thrown when access Childtable data from ParentData List
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) Exception Stack Trace :
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)
As part of migration, I have updated
- Hibernate version
- Hibernate5.LocalSessionFactoryBean
- Hibernate5.HibernateTransactionManager
If I revert back above mentioned changes for Hibernate4, the code works fine in Multi-Threaded Pool, but throws ConcurrentModificationException with above updates.
I am sure I must be missing something very silly OR some setting which needs to be added as part of Hibernate 5 migration.
Any suggestion is greatly appreciated.
Thanks in advance.