⇧ amazing...
Spring Batchで外部ファイルを渡してバッチ処理させたい
調べた感じでは、
⇧ stackoverflowにしか情報が載ってなかったかな...
Spring Batchって日本だとあんまり使われてないんかな?
Spring Batchで外部ファイルを渡してバッチ処理させてみる
というわけで、試してみました。
プロジェクトは、
⇧ 上記の記事のものを利用してます。
手を加えたファイルは以下。
変更、追加したファイルの内容を記載。
■/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の残念過ぎるところ...
毎度モヤモヤ感が半端ない...
今回はこのへんで。