JpaItemWriter in Spring batch: no transaction is in progress

Viewed 33

i'd like to use JpaItemWriter in conditions of multi-datasource in spring batch, JPA. below is my config (there are log, common, member db config)

  1. common db config
@Configuration
@EnableJpaRepositories(
    basePackages = "com.member.batch.dao.common",
    entityManagerFactoryRef = "commonEntityManager",
    transactionManagerRef = "commonTransactionManager"
)
public class CommonConfiguration {

    @ConfigurationProperties(prefix = "spring.datasource-common.hikari")
    @Primary
    @Bean(name = "commonDataSource")
    public DataSource commonDataSource() {
        return DataSourceBuilder.create().type(HikariDataSource.class).build();
    }

    @ConfigurationProperties("spring.jpa.common-properties")
    @Bean(name = "commonHibernateProperties")
    public HashMap<String, Object> hibernateProperties() {
        return new HashMap<>();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean commonEntityManager() {
        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(commonDataSource());
        em.setPackagesToScan("com.member.batch.entities.common");

        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);

        final HashMap<String, Object> properties = hibernateProperties();
        em.setJpaPropertyMap(properties);

        return em;
    }

    @Bean(name = "commonTransactionManager")
    public PlatformTransactionManager commonTransactionManager() {
        JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(commonEntityManager().getObject());
        return transactionManager;
    }
}

  1. log db config

@Configuration
@EnableJpaRepositories(
        basePackages = "com.member.batch.dao.log",
        entityManagerFactoryRef = "logEntityManager",
        transactionManagerRef = "logTransactionManager"
)
public class LogConfiguration {
// same format with common
}

  1. member db config

@Configuration
@EnableJpaRepositories(
    basePackages = "com.member.batch.dao.member",
    entityManagerFactoryRef = "memberEntityManager",
    transactionManagerRef = "memberTransactionManager"
)
public class MemberConfiguration {
 
    // same format with common
}

and i have to use configures in my job

@Slf4j
@RequiredArgsConstructor
@Configuration
public class WithdrawMembersJobConfiguration {

    public static final String WITHDRAW_MEMBERS_JOB = "withdrawMembersJob";

    private final MessageSource messageSource;

    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;
    private final CustomJobListener customJobListener;
    private final ScheduleJobService scheduleJobService;

    private final EntityManagerFactory entityManagerFactory;

    @Bean
    @Primary
    public Job withdrawMembersJob() {
        return jobBuilderFactory.get(WITHDRAW_MEMBERS_JOB)
                .start(withdrawStep())
                .listener(customJobListener)
                .build();
    }

    @JobScope
    @Bean(name = WITHDRAW_MEMBERS_JOB + "_step")
    public Step withdrawStep() {
        SimpleStepBuilder step = stepBuilderFactory.get("deleteExpiredMembersStep")
                .<WithdrawMember, WithdrawMember>chunk(1000)
                .reader(withdrawItemReader(null, null))
                .processor(withdrawItemProcessor())
                .writer(withdrawItemWriter());
        return step.build();
    }

    @StepScope
    @Bean(name = WITHDRAW_MEMBERS_JOB + "_reader")
    public JpaPagingItemReader<WithdrawMember> withdrawItemReader(@org.springframework.beans.factory.annotation.Value("#{jobParameters[requestDate]}") String requestDate,
                                                                  @org.springframework.beans.factory.annotation.Value("#{jobParameters[status]}") WithdrawStatus status) {
        LocalDateTime scheduledDate = LocalDateTime.now().with(LocalTime.MAX);
        log.info("scheduled date is " + scheduledDate);

        if (status == null) {
            status = WithdrawStatus.WSC001;
        }

        Map<String, Object> parameters = new HashMap<>();
        parameters.put("scheduledDate", scheduledDate);
        parameters.put("status", status);
        return new JpaPagingItemReaderBuilder<WithdrawMember>()
                .name("withdrawReader")
                .parameterValues(parameters)
                .entityManagerFactory(entityManagerFactory)
                .queryString("select m from WithdrawMember m where m.scheduleDate < :scheduledDate and m.status = :status")
                .pageSize(1000)
                .build();
    }


    @StepScope
    @Bean(name = WITHDRAW_MEMBERS_JOB + "_processor")
    public ItemProcessor<WithdrawMember, WithdrawMember> withdrawItemProcessor() {
        return new ItemProcessor<WithdrawMember, WithdrawMember>() {
            @Override
            public WithdrawMember process(WithdrawMember item) throws Exception {
                log.info("withdraw info , " + item.getReason());
                return item;
            }
        };
    }

    @StepScope
    @Bean(name = WITHDRAW_MEMBERS_JOB + "_writer")
    public JpaItemWriter withdrawItemWriter() {
        return new JpaItemWriterBuilder<WithdrawMember>()
                .entityManagerFactory(entityManagerFactory)
                .build();
    }

    }

   

but now i am having getting error ,below is my console

2022-09-15 16:18:01.521 DEBUG 1552 --- [eduler_Worker-4] o.s.j.d.DataSourceTransactionManager     : Creating new transaction with name [org.springframework.batch.core.repository.support.SimpleJobRepository.update]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2022-09-15 16:18:01.521 DEBUG 1552 --- [eduler_Worker-4] o.s.j.d.DataSourceTransactionManager     : Acquired Connection [HikariProxyConnection@238016139 wrapping com.mysql.cj.jdbc.ConnectionImpl@3023ef72] for JDBC transaction
2022-09-15 16:18:01.521 DEBUG 1552 --- [eduler_Worker-4] o.s.j.d.DataSourceTransactionManager     : Switching JDBC Connection [HikariProxyConnection@238016139 wrapping com.mysql.cj.jdbc.ConnectionImpl@3023ef72] to manual commit
2022-09-15 16:18:01.541 DEBUG 1552 --- [eduler_Worker-4] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL query
2022-09-15 16:18:01.541 DEBUG 1552 --- [eduler_Worker-4] o.s.jdbc.core.JdbcTemplate               : Executing prepared SQL statement [SELECT VERSION FROM BATCH_JOB_EXECUTION WHERE JOB_EXECUTION_ID=?]
2022-09-15 16:18:01.577 DEBUG 1552 --- [eduler_Worker-4] o.s.b.c.r.dao.JdbcJobExecutionDao        : Truncating long message before update of JobExecution: JobExecution: id=1956, version=1, startTime=Thu Sep 15 16:18:00 KST 2022, endTime=Thu Sep 15 16:18:01 KST 2022, lastUpdated=Thu Sep 15 16:18:01 KST 2022, status=FAILED, exitStatus=exitCode=FAILED;exitDescription=javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:422)
    at org.hibernate.internal.SessionImpl.checkTransactionNeededForUpdateOperation(SessionImpl.java:3397)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1354)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1349)

javax.persistence.TransactionRequiredException: no transaction is in progress
    at org.hibernate.internal.AbstractSharedSessionContract.checkTransactionNeededForUpdateOperation(AbstractSharedSessionContract.java:422) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl.checkTransactionNeededForUpdateOperation(SessionImpl.java:3397) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1354) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1349) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:362) ~[spring-orm-5.3.9.jar:5.3.9]
    at com.sun.proxy.$Proxy119.flush(Unknown Source) ~[na:na]
    at org.springframework.batch.item.database.JpaItemWriter.write(JpaItemWriter.java:94) ~[spring-batch-infrastructure-4.3.3.jar:4.3.3]
    at org.springframework.batch.item.database.JpaItemWriter$$FastClassBySpringCGLIB$$29c4242e.invoke(<generated>) ~[spring-batch-infrastructure-4.3.3.jar:4.3.3]
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) ~[spring-core-5.3.9.jar:5.3.9]
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) ~[spring-aop-5.3.9.jar:5.3.9]
    at 

i don't know why i get this error. i could get result of itemReader.

1 Answers

None of the classes you shared is annotated with @EnableBatchProcessing, so it is not clear which transaction manager is set on the StepBuilderFactory you use to create steps. The default transaction manager configured by @EnableBatchProcessing is a DataSourceTransactionManager, which knows nothing about the JPA context. Since you use JPA, so you need to configure a JpaTransactionManager in your step:

@JobScope
@Bean(name = WITHDRAW_MEMBERS_JOB + "_step")
public Step withdrawStep(JpaTransactionManager transactionManager) {
   SimpleStepBuilder step = stepBuilderFactory.get("deleteExpiredMembersStep")
      .<WithdrawMember, WithdrawMember>chunk(1000)
      .transactionManager(transactionManager)
      .reader(withdrawItemReader(null, null))
      .processor(withdrawItemProcessor())
      .writer(withdrawItemWriter());
   return step.build();
}

If you use @EnableBatchProcessing, you need to provide a custom BatchConfigurer, something like:

@Bean
public BatchConfigurer batchConfigurer(DataSource dataSource, EntityManagerFactory entityManagerFactory) {
    return new DefaultBatchConfigurer(dataSource) {
        @Override
        public PlatformTransactionManager getTransactionManager() {
            return new JpaTransactionManager(entityManagerFactory);
        }
    };
}
Related