0
votes

I am working on a spring batch which reads from a csv file and writes into database. i am using FlatFileItemReader for reading the file and implemented ItemWriter which uses Jpa to insert data into database. But batch fails with no transaction in progress. Here is my job configuration

<bean id="datasource" class="oracle.jdbc.pool.OracleDataSource">
    <property name="user" value="xxx" />
    <property name="password" value="xx" />
    <property name="URL" value="xxx" />
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>

<bean id="entityManagerFactory" name="model"
      class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="datasource"/>

    <property name="jpaVendorAdapter">
        <bean
                class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="generateDdl" value="false"/>
            <property name="showSql" value="true"/>
            <property name="database">
                <util:constant
                        static-field="org.springframework.orm.jpa.vendor.Database.ORACLE"/>
            </property>
            <property name="databasePlatform" value="org.hibernate.dialect.Oracle12cDialect"/>
        </bean>
    </property>
</bean>

<batch:job id="testJob">
    <batch:step id="step">
        <batch:tasklet>
            <batch:chunk reader="cvsFileItemReader" writer="databaseWriter"
                         commit-interval="10">
            </batch:chunk>
        </batch:tasklet>
    </batch:step>
</batch:job>

And below is my writer

public class DatabaseWriter implements ItemWriter<Report> {


private EntityManagerFactory entityManagerFactory;

@Autowired
public DatabaseWriter(EntityManagerFactory entityManagerFactory) {
    this.entityManagerFactory = entityManagerFactory;
}

@Override
public void write(List<? extends Report> list) throws Exception {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    for (Report report : list) {


        entityManager.persist(report );
    }
    entityManager.flush();

}

}

It only works if start transaction explicitly .that is like below

public class DatabaseWriter implements ItemWriter<Report> {


private EntityManagerFactory entityManagerFactory;

@Autowired
public DatabaseWriter(EntityManagerFactory entityManagerFactory) {
    this.entityManagerFactory = entityManagerFactory;
}

@Override
public void write(List<? extends Report> list) throws Exception {
    EntityManager entityManager = entityManagerFactory.createEntityManager();
    entityManager.getTransaction().begin();
    for (Report report : list) {

        entityManager.persist(report );
    }
    entityManager.getTransaction().commit();

}

}

Is it really needed that transaction should be maintained explicitly and spring batch doesn't control it out of box?

EDIT

I fixed it by changing writer like

public class DatabaseWriter implements ItemWriter<Report> {


@PersistenceContext
private EntityManager entityManager;

@Override

public void write(List<? extends Report> list)  {
    for (Report report : list) {
        entityManager.persist(report );
    }


}

Changing EntityMangerFactory to EntityManager with PersistenceContext fixed the problem. But i am struggling to understand reason for this behaviour

2

2 Answers

1
votes

Create DAO and move your JPA code there and autowire the DAO in your writer.

Also make sure you autowire the entity manager like below.

@PersistenceContext
private EntityManager em;
1
votes

But i am struggling to understand reason for this behaviour

The first approach does not work because you create a transaction manually while your code is actually running within the scope of a transaction driven by Spring Batch. So there will be two transactions with different contexts.

You should keep in mind that a tasklet is executed in a transaction driven by Spring Batch (including the item writer for a chunk-oriented tasklet). So any code running in that scope should conform to the transaction definition of the tasklet. In your case, the EntityManager injected in the writer is driven by the JpaTransactionManager you defined, which is also used by Spring Batch for the tasklet's transaction.

As a side note, you can use the JpaItemWriter provided by Spring Batch instead of writing a custom writer. The code is almost identical.