※当サイトの記事には、広告・プロモーションが含まれます。

Spring Batchで外部ファイルを渡してバッチ処理させたい

nazology.net

⇧ amazing...

Spring Batchで外部ファイルを渡してバッチ処理させたい

調べた感じでは、

stackoverflow.com

stackoverflow.com

stackoverflow.com

⇧ stackoverflowにしか情報が載ってなかったかな...

Spring Batchって日本だとあんまり使われてないんかな?

Spring Batchで外部ファイルを渡してバッチ処理させてみる

というわけで、試してみました。

プロジェクトは、

ts0818.hatenablog.com

⇧ 上記の記事のものを利用してます。

手を加えたファイルは以下。

変更、追加したファイルの内容を記載。

■/mybatis-example/src/main/resources/application.properties

# データベース接続
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5434/test
spring.datasource.username=postgres
spring.datasource.password=postgres

# MyBatisでEntityクラスのフィールド名とデータベースのテーブルのカラム名の対応
mybatis.configuration.map-underscore-to-camel-case=true

# バッチ処理が自動で実行されないように設定
spring.batch.job.enabled=false

# ファイルアップロード
spring.servlet.multipart.max-file-size=128KB
spring.servlet.multipart.max-request-size=128KB
spring.servlet.multipart.location=${java.io.tmpdir}    

■/mybatis-example/src/main/java/com/example/demo/batch/user/UserJobLauncher.java

package com.example.demo.batch.user;

import java.util.HashMap;
import java.util.Map;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.stereotype.Component;

import lombok.RequiredArgsConstructor;

@EnableScheduling
@Component
@RequiredArgsConstructor
public class UserJobLauncher {

	private final JobLauncher jobLauncher;
	
	private final Job userJob;
	
	private JobParameters jobParameters;
	
	public BatchStatus launchUserJob(String filePath) throws JobExecutionAlreadyRunningException, JobRestartException,
			JobInstanceAlreadyCompleteException, JobParametersInvalidException {
        Map<String, JobParameter> confMap = new HashMap<>();
        confMap.put("time", new JobParameter(System.currentTimeMillis()));
        confMap.put("filePath", new JobParameter(filePath));
        this. jobParameters = new JobParameters(confMap);
		JobExecution jobExecution = this.jobLauncher.run(this.userJob, this.jobParameters);
		return jobExecution.getStatus();
	}
	
}    

■/mybatis-example/src/main/java/com/example/demo/batch/user/UserBatchConfig.java

package com.example.demo.batch.user;

import java.io.IOException;
import java.nio.charset.StandardCharsets;

import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
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.core.job.flow.FlowExecutionStatus;
import org.springframework.batch.core.job.flow.JobExecutionDecider;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;

import com.example.demo.dto.CsvUser;
import com.example.demo.entity.ShikokuOhenroUser;

@Configuration
@EnableBatchProcessing
public class UserBatchConfig {

	@Autowired
	private JobBuilderFactory jobBuilderFactory;
	
	@Autowired
	private StepBuilderFactory stepBuilderFactory;
	
//	@Bean
//	@StepScope
//	public Resource userCsvResource(@Value("#{jobParameters['filePath']}") String filePath) {
//		return new FileSystemResource(filePath);
//	}
	
    @Bean
    @StepScope
    public UserReader<CsvUser> csvUserReader(CsvUserMapper csvUserMapper, @Value("#{jobParameters['filePath']}") String filePath) throws IOException {
        UserReader<CsvUser> reader = new UserReader<>();
        reader.setCharset(StandardCharsets.UTF_8);
        reader.setStrict(true);
        //reader.setResource(new ClassPathResource(filePath));
        reader.setResource(new FileSystemResource(filePath));
        reader.setLinesToSkip(1);
        reader.setHeaders(new String[] {"last_name", "first_name", "rome_last_name", "rome_first_name", "ohenro_ids"});
        reader.setFieldSetMapper(csvUserMapper);
        reader.setDelimiter(',');
        reader.setQuotedChar('"');
        reader.setName("csvReader");
        return reader;
    }
    
    @Bean
    public Step step1(UserReader<CsvUser> userReader, UserWriter userWriter, UserProcessor userProcessor) {
        return stepBuilderFactory.get("csvItemReaderStep")
                .<CsvUser, ShikokuOhenroUser> chunk(10)
                .reader(userReader)
                .processor(userProcessor)
                .writer(userWriter)
                .build();
    }
    
//    @Bean
//    public Job importUserJob(Step step1) {
//    	return jobBuilderFactory.get("importUserJob")
//    			.incrementer(new RunIdIncrementer())
//    			.start(step1)
//    			.build();
//    }
    
    @Bean
    public Job userJob(Step step1) {
    	return jobBuilderFactory.get("userJob")
    			.incrementer(new RunIdIncrementer())
    			.start(step1)
    			  .next(decider())
    			  .on("Success")
    			  .end()
    			  .build()
    			.build();
    }

    @Bean
    public JobExecutionDecider decider() {
        System.out.println("Made it to the decider");
        return (jobExecution, stepExecution) -> new FlowExecutionStatus("Success"); 
    }

}

■/mybatis-example/src/main/java/com/example/demo/controller/rest/ShikokuOhenroController.java

package com.example.demo.controller.rest;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.example.demo.batch.user.UserJobLauncher;
import com.example.demo.entity.ShikokuOhenro;
import com.example.demo.entity.custom.CustomShikokuOhenro;
import com.example.demo.service.CustomShikokuOhenroServiceImpl;
import com.example.demo.service.ShikokuOhenroServiceImpl;

@RestController
public class ShikokuOhenroController {

	@Autowired
	private ShikokuOhenroServiceImpl shikokuOhenroServiceImpl;
	
	@Autowired
	private CustomShikokuOhenroServiceImpl customShikokuOhenroServiceImpl;
	
	@Autowired
	private UserJobLauncher userJobLauncher;
	
	@Value("${spring.servlet.multipart.location}")
	private String multipartFileLocation;
	
	@GetMapping("find/{id}")
	public ShikokuOhenro findById(@PathVariable("id") Integer id) {
		return shikokuOhenroServiceImpl.findById(id);
	}
	
//	@GetMapping("find-all")
//	public List<ShikokuOhenro> findAll() {
//		return shikokuOhenroServiceImpl.findAll();
//	}
	
	@GetMapping("find-shikoku-ohenro-and-prefectures")
	public List<CustomShikokuOhenro> findShikokOhenroAndPrefectures() {
		return customShikokuOhenroServiceImpl.findShikokuOhenroAndPrefectures();
	}
	
	@PostMapping("user-insert")
	public BatchStatus userInsert(@RequestParam("file") MultipartFile multipartFile)
			throws JobExecutionAlreadyRunningException, JobRestartException, JobInstanceAlreadyCompleteException,
			JobParametersInvalidException, IllegalStateException, IOException {
		//String rootPath = System.getProperty("catalina.home");
		File uploadFile = new File(this.multipartFileLocation + File.separator + multipartFile.getOriginalFilename());
		multipartFile.transferTo(uploadFile);
		return userJobLauncher.launchUserJob(uploadFile.getAbsolutePath());
	}

}

■/mybatis-example/src/main/java/com/example/demo/controller/UserController.java

package com.example.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class UserController {
	@GetMapping("/")
	public String index() {
		return "user/index";
	}
}    

■/mybatis-example/src/main/resources/templates/user/index.html

<!DOCTYPE html>
<html lang="jp" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
	<div>
		<form method="POST" enctype="multipart/form-data" action="/user-insert">
			<table>
				<tr>
				  <td>File to upload:</td>
				  <td><input type="file" name="file" /></td>
				</tr>
				<tr>
				  <td></td>
				  <td><input type="submit" value="Upload" /></td>
				</tr>
			</table>
		</form>
	</div>
</body>
</html>    

で、実行して、ブラウザにアクセスしてCSVファイルをアップロードしてみる。

アップロードしたCSVファイルのデータがINSERTされました。

Spring Batchも使い辛いけど、Thymeleafも使い辛いことに気付いた今日この頃...

そして、相変わらず、MultipartFileからパスが取得できないのもSpring Frameworkの残念過ぎるところ...

毎度モヤモヤ感が半端ない...

今回はこのへんで。