1

I am learning Spring batch jobs with hibernate and I am facing one issue.

TransactionRequiredException: no transaction is in progress

I have create reader, processor and writer. I am updating user in processor and after writer I am getting this error. I have tried with @Transacional method on processor but It is not working. I am not sure what is wrong here. Adding my job configuration file. Before this one I am facing issue related to transaction manager. I am also not sure about transaction manager I used is proper one or not. Please let me know where I am making mistake.

import java.util.Properties;
import javax.sql.DataSource;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.launch.support.SimpleJobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.JobRepositoryFactoryBean; 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
public class JobConfiguration {

@Bean
public DataSource jobDataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/TestDB");
    dataSource.setUsername("****");
    dataSource.setPassword("****");
    return dataSource;
}

private JobRepository getJobRepository() throws Exception {
    JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
    factory.setDataSource(jobDataSource());
    factory.setTransactionManager(getTransactionManager());
    factory.afterPropertiesSet();
    return (JobRepository) factory.getObject();
}

private PlatformTransactionManager getTransactionManager() {
    HibernateTransactionManager txManager = new HibernateTransactionManager();
    txManager.setSessionFactory(jobSessionFactory().getObject());
    return txManager;
}

@Bean
public JobLauncher myJobLauncher() throws Exception {
    SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
    jobLauncher.setJobRepository(getJobRepository());
    jobLauncher.afterPropertiesSet();
    return jobLauncher;
}

@Bean
public LocalSessionFactoryBean jobSessionFactory() {
    LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
    sessionFactory.setDataSource(jobDataSource());
    sessionFactory.setPackagesToScan("com.test");
    sessionFactory.setHibernateProperties(jobHibernateProperties());
    return sessionFactory;
}

@Bean
public Properties jobHibernateProperties() {
    Properties hibernateProperties = new Properties();
    hibernateProperties.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
    hibernateProperties.put("hibernate.show_sql", false);
    hibernateProperties.put("hibernate.hbm2ddl.auto", "update");
    hibernateProperties.put("hibernate.format_sql", true);
    return hibernateProperties;
}
}

TestJob.Java

import org.hibernate.SessionFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import 
org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import 
org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.step.tasklet.Tasklet;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.ParseException;
import org.springframework.batch.item.UnexpectedInputException;
import org.springframework.batch.item.database.HibernateCursorItemReader;
import org.springframework.batch.item.database.builder.HibernateCursorItemReaderBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.cadau.jobs.dto.TestDTO;
import com.Test.model.User;

@Configuration
public class TestJob {

@Autowired
private SessionFactory sessionFactory;

@Autowired
private JobBuilderFactory jobBuilderFactory;

@Autowired
private StepBuilderFactory steps;

@Bean(name = "firstBatchJob")
public Job job(@Qualifier("step1")Step step1) {
    return jobBuilderFactory.get("firstBatchJob")
            .start(step1)
            .build();
}

@Bean(name="step1")
protected Step step1(ItemReader<User> itemReader,
  ItemProcessor<User, User> testProcessor,
  ItemWriter<User> testWriter) {
    return steps.get("step1").<User, User> chunk(10)
      .reader(itemReader)
      .processor(testProcessor)
      .writer(testWriter)
      .build();
}

@Bean(value = "itemReader")
public ItemReader itemReader() throws UnexpectedInputException, ParseException {
    HibernateCursorItemReader<User> reader = new HibernateCursorItemReader<>();
    reader.setQueryString("from User");
    reader.setFetchSize(1000);
    reader.setSessionFactory(sessionFactory);
    reader.setUseStatelessSession(true);
    return reader;
}
}

TestProcessor.java

@Transactional
@Component(value = "testProcessor")
@StepScope
public class TestProcessor implements ItemProcessor<User, User> {

@Autowired
private IUserService userService;

@Override
public User process(User item) throws Exception {
    System.out.println("Processor:::  "+item);
    User user = userService.edit(item.getId());
    System.out.println("DB user : "+user);
    user.setFullName("Test User");
    userService.update(user);
    System.out.println("After update user" + user);
    return item;
}
}

Testwriter.java

@StepScope
@Component(value = "testWriter")
public class TestWriter implements ItemWriter<User>{

@Override
public void write(List<? extends User> items) throws Exception {
    System.out.println("Writer called.");
}
}

Adding error as well

2020-05-23 14:59:05,098 INFO  [restartedMain] org.springframework.batch.core.launch.support.SimpleJobLauncher$1: Job: [SimpleJob: [name=firstBatchJob]] launched with the following parameters: [{}]
2020-05-23 14:59:06,679 INFO  [restartedMain] org.springframework.batch.core.job.SimpleStepHandler: Executing step: [step1]
2020-05-23 14:59:14,965 INFO  [restartedMain] org.hibernate.hql.internal.QueryTranslatorFactoryInitiator: HHH000397: Using ASTQueryTranslatorFactory
Processor:::  com.cadau.model.User@3eae26f1
DB user : com.cadau.model.User@3edb84f6
After update usercom.cadau.model.User@3edb84f6
Processor:::  com.cadau.model.User@3b237ad2
DB user : com.cadau.model.User@2c527a91
After update usercom.cadau.model.User@2c527a91
Writer called.
2020-05-23 14:59:24,011 INFO  [restartedMain] org.springframework.batch.core.step.tasklet.TaskletStep$ChunkTransactionCallback: Commit failed while step execution data was already updated. Reverting to old version.
2020-05-23 14:59:24,018 ERROR [restartedMain] org.springframework.batch.core.step.AbstractStep: Encountered an error executing step step1 in job firstBatchJob
javax.persistence.TransactionRequiredException: no transaction is in progress
at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:398)
at org.hibernate.internal.SessionImpl.checkTransactionNeededForUpdateOperation(SessionImpl.java:3558)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1444)
at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1440)
at org.springframework.orm.hibernate5.SessionFactoryUtils.flush(SessionFactoryUtils.java:147)
at org.springframework.orm.hibernate5.SpringSessionSynchronization.beforeCommit(SpringSessionSynchronization.java:95)
at org.springframework.transaction.support.TransactionSynchronizationUtils.triggerBeforeCommit(TransactionSynchronizationUtils.java:96)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.triggerBeforeCommit(AbstractPlatformTransactionManager.java:922)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:127)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy126.commit(Unknown Source)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:152)
at org.springframework.batch.core.step.tasklet.TaskletStep$2.doInChunkContext(TaskletStep.java:273)
at org.springframework.batch.core.scope.context.StepContextRepeatCallback.doInIteration(StepContextRepeatCallback.java:82)
at org.springframework.batch.repeat.support.RepeatTemplate.getNextResult(RepeatTemplate.java:375)
at org.springframework.batch.repeat.support.RepeatTemplate.executeInternal(RepeatTemplate.java:215)
at org.springframework.batch.repeat.support.RepeatTemplate.iterate(RepeatTemplate.java:145)
at org.springframework.batch.core.step.tasklet.TaskletStep.doExecute(TaskletStep.java:258)
at org.springframework.batch.core.step.AbstractStep.execute(AbstractStep.java:203)
at org.springframework.batch.core.job.SimpleStepHandler.handleStep(SimpleStepHandler.java:148)
at org.springframework.batch.core.job.AbstractJob.handleStep(AbstractJob.java:399)
at org.springframework.batch.core.job.SimpleJob.doExecute(SimpleJob.java:135)
at org.springframework.batch.core.job.AbstractJob.execute(AbstractJob.java:313)
at org.springframework.batch.core.launch.support.SimpleJobLauncher$1.run(SimpleJobLauncher.java:144)
at org.springframework.core.task.SyncTaskExecutor.execute(SyncTaskExecutor.java:50)
at org.springframework.batch.core.launch.support.SimpleJobLauncher.run(SimpleJobLauncher.java:137)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:343)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:198)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.batch.core.configuration.annotation.SimpleBatchConfiguration$PassthruAdvice.invoke(SimpleBatchConfiguration.java:127)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy124.run(Unknown Source)
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.execute(JobLauncherCommandLineRunner.java:207)
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.executeLocalJobs(JobLauncherCommandLineRunner.java:181)
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.launchJobFromProperties(JobLauncherCommandLineRunner.java:168)
at org.springframework.boot.autoconfigure.batch.JobLauncherCommandLineRunner.run(JobLauncherCommandLineRunner.java:163)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:780)
at org.springframework.boot.SpringApplication.callRunners(SpringApplication.java:764)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:319)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1214)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1203)
at com.cadau.jobs.app.CadauJobsApplication.main(CadauJobsApplication.java:27)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
2020-05-23 14:59:24,432 INFO  [restartedMain] org.springframework.batch.core.launch.support.SimpleJobLauncher$1: Job: [SimpleJob: [name=firstBatchJob]] completed with the following parameters: [{}] and the following status: [FAILED]
Aks 1316
  • 372
  • 5
  • 19
  • Where do you get the exception? Please post the code – Simon Martinelli May 23 '20 at 08:57
  • @SimonMartinelli. I have added code plus exception I am getting. I am getting exception after execution of writer. You can see in log. It is printing my code of writer and then I am getting error. May be I am getting this error when committing transaction. But not sure – Aks 1316 May 23 '20 at 09:39

2 Answers2

2

Since you are using Hibernate, you need to configure Spring Batch to use a HibernateTransactionManager to drive transactions. As mentioned in the reference documentation, the way to do that is by defining a BatchConfigurer and overriding getTransactionManager(). However, this is not what your code does:

private PlatformTransactionManager getTransactionManager() {
   HibernateTransactionManager txManager = new HibernateTransactionManager();
   txManager.setSessionFactory(jobSessionFactory().getObject());
   return txManager;
}

You can make JobConfiguration extend DefaultBatchConfigurer:

@Configuration
public class JobConfiguration extends DefaultBatchConfigurer {

   @Override
   public HibernateTransactionManager getTransactionManager() {
      HibernateTransactionManager txManager = new HibernateTransactionManager();
      txManager.setSessionFactory(jobSessionFactory().getObject());
      return txManager;
   }

}

This also the way to do it if you use @EnableBatchProcessing, see its Javadoc in the section starting with "In order to use a custom transaction manager, a custom BatchConfigurer should be provided. For example:" ...

Mahmoud Ben Hassine
  • 28,519
  • 3
  • 32
  • 50
0

First, you have to save the items inside the writer and not in the processor. The processor is used for optional transformations while items are passed from reader to writer. You dont need the @Transactional on the processor. Transactions are configured by default. The transaction manager can optionally be configured in the StepBuilderFactory steps.get("step1").transactionManager(transactionManager), but this is usually done by default. Just declate the transactionManager as a bean.

Lastly, you can use @EnableBatchProcessing and remove JobConfiguration completely.

Rasha Elsayed
  • 660
  • 1
  • 7
  • 22
  • Hi, Rasha Elsayed Thanks for reply. But If I am declaring transactionManager as a bean I am getting following error. org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available: expected single matching bean but found 2: getTransactionManager,transactionManager I have referred this one site : https://www.baeldung.com/introduction-to-spring-batch – Aks 1316 May 23 '20 at 16:44
  • did you try to remove @Transactional annotation as well as the userService.update from the processor to the writer? BTW you don't need a custom writer, you can configure JpaItemWriter or JdbcBatchItemWriter – Rasha Elsayed May 25 '20 at 13:31