2

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.

1 Answers1

1

An Hibernate Session and the objects loaded from that session, altogether, are not thread safe. So you cannot load objects from a session and then use these objects in different threads.

Among other problems these objects might trigger the initialization of lazy collection/proxies, which ultimately falls on the underlying JDBC connection and that connection is not thread safe either.

You need to give each thread it's own session and you must not share objects loaded from sessions between threads

Guillaume
  • 14,306
  • 3
  • 43
  • 40
  • My concern is it worked in Hibernate4 but not in 5. So believe if I have to add any setting? – functionalInterface Apr 13 '21 at 15:21
  • I've also noticed that Hibernate 5 is more likely to fail with multi threaded code (compared to Hibernate 4), but that's the thing we non thread safe code: even if it might work in some cases you really should not do it. – Guillaume Apr 13 '21 at 16:33