pollpoll
pollpoll

Reputation: 15

How do you run the `Job` in the spring batch in a production environment

I implemented now with spring batch to manage dormant account. The following problems occurred while using the spring batch.

Spring Batch When I run using joblauncher, I think the job is not running

The rough content of the article is that the method with @Bean cannot be executed because it is managed as a single tone in the spring.

I wanted to run Job somehow using the @Scheduled and solved it with the following solution.

  1. Replace the @Configuration in the batch class I created with @Component.
  2. Erase all @Bean on the Job, Step, Reader, Processor, and Writer.

After doing so, Job worked as I wanted and got a log.

I solved the problem, but there was another problem.

In a lot of example, even spring official documents attached @Bean at all of Job.

Furthermore, it seemed that JobParameter could not be used without @Bean attached. (When I tried to use the JobParameter, the initial value was null.)

I'm going to ask you a real question now.

Spring Batch will also be used in the field. And it's expected to put @Bean, as it says in the official document. So, how do you run a Job in the field?

Please let me know where I need to fix in my code.

package com.capston.chatting.service.scheduler;

import com.capston.chatting.config.batch.InactiveMemberJob;
import com.capston.chatting.entity.Member;
import com.capston.chatting.enums.MemberStatus;
import com.capston.chatting.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.*;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class ScheduleService {

    private final InactiveMemberJob config;
    private final JobLauncher jobLauncher;
    private final MemberRepository memberRepository;
    private final JobExplorer jobExplorer;

    //test

    @Scheduled(cron = "0/5 * * * * *")
    public void runInactiveMemberScheduler() {
        try {
            jobLauncher.run(
                    config.inactiveMemberJob(), new JobParametersBuilder(jobExplorer)
                            .getNextJobParameters(config.inactiveMemberJob())
                            .addString("requestDate", LocalDateTime.now().toString().substring(0, 16))
                            .addString("test", "Test")
                            .toJobParameters()
            );
        } catch(Exception e) {
            log.error(e.getMessage());
        }
    }
}
package com.capston.chatting.config.batch;

import com.capston.chatting.entity.Member;
import com.capston.chatting.enums.MemberStatus;
import com.capston.chatting.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

@Slf4j
@RequiredArgsConstructor
@Component
public class InactiveMemberJob {

    private final MemberRepository memberRepository;
    private final JobBuilderFactory jobBuilderFactory;
    private final StepBuilderFactory stepBuilderFactory;

    public Job inactiveMemberJob() {
        log.info("InactiveMemberJob execution");
        return jobBuilderFactory.get("inactiveMemberJob")
                .start(inactiveJobStep())
                .preventRestart()
                .incrementer(new UniqueRunIdIncrementer())
                .build();
    }

    public Step inactiveJobStep() {
        log.info("InactiveMemberStep execution");
        return stepBuilderFactory.get("inactiveMemberStep")
                .<Member, Member>chunk(10)
                .reader(inactiveMemberReader(null))
                .processor(inactiveMemberProcessor())
                .writer(inactiveMemberWriter())
                .allowStartIfComplete(true)
                .build();
    }

    @StepScope
    public ListItemReader<Member> inactiveMemberReader(@Value("#{jobparameters[test]}") String test) {
        log.info("InactiveMemberReader execution");
        log.info("JobParameters : {}", test);

        List<Member> oldMembers = memberRepository
                .findByUpdateDateBeforeAndStatusEquals(LocalDateTime.now().minusYears(1), MemberStatus.ACTIVE);

        return new ListItemReader<>(oldMembers);
    }

    public ItemProcessor<Member, Member> inactiveMemberProcessor() {
        log.info("test");
        ItemProcessor<Member, Member> memberItemProcessor = (member) -> {
            log.info("InactiveMemberProcessor execution");
            return member.setInactive();
        };
        return memberItemProcessor;
//        return new ItemProcessor<Member, Member>() {
//            @Override
//            public Member process(Member member) throws Exception {
//                log.info("InactiveMemberProcessor execution");
//                return member.setInactive();
//            }
//        };
//        return member -> {
//            log.info("InactiveMemberProcessor execution");
//            return member.setInactive();
//        };
    }

    public ItemWriter<Member> inactiveMemberWriter() {
        log.info("InactiveMemberWriter execution");
        return ((List<? extends Member> members) -> {
            memberRepository.saveAll(members);
        });
    }
}

Upvotes: 0

Views: 517

Answers (1)

pete_bc
pete_bc

Reputation: 76

When you annotate a method with @Bean, you are asking the Spring container to call that method to create an instance of a the method's return type with the indicated scope. The default scope is Application scope, which is one instance for the application, also known as singleton. The annotation @StepScope has no meaning on a method that is not also annotated as @Bean. It tells the container to provide one instance of the class for the current Step -- not just one for the application. This sets a context in which job parameters make sense, since the Step is within the context of the Job for which those parameters are defined. The spring property referenced by the @Value annotation will be evaluated by the Spring container when it creates the bean, which it will do only if the method is annotated with @Bean (or a class annotated with @Component or an extension of that annotation). This is why, when inactiveMemberReader(null) is called to define the reader in your Step, the job parameter "test" has a null value. Without the @Bean annotation telling the Spring container to create it for the Step, it is just a plain method being passed null as the value for the test String argument.

Upvotes: 1

Related Questions