Spring Batch + JPA 處理 Excel 檔案教程

banq發表於2024-07-18

在本文中,我們將演示如何使用 Spring Batch 從 Excel (.xls 或 .xlsx) 檔案讀取所有行並將其儲存到 Spring Boot 應用程式中的資料庫中。我們將介紹從讀取 Excel 檔案、將行轉換為Entity例項、將這些例項儲存到資料庫、記錄進度以及使用 cron 表示式安排批處理作業的整個過程。

前提:
確保您具有以下資訊:

  • JDK 17 或更高版本
  • Maven 或 Gradle
  • Spring Boot 3.2.7 或更高版本
  • H2 或任何其他資料庫
  • Apache POI 用於讀取 Excel 檔案


pom.xml:

<dependencies>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-batch</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-data-jpa</artifactId>
     </dependency>
     <dependency>
         <groupId>org.springframework.batch.extensions</groupId>
         <artifactId>spring-batch-excel</artifactId>
         <version>0.1.1</version>
     </dependency>
     <dependency>
         <groupId>org.apache.poi</groupId>
         <artifactId>poi-ooxml</artifactId>
         <version>5.2.5</version>
     </dependency>
     <dependency>
         <groupId>com.h2database</groupId>
         <artifactId>h2</artifactId>
         <scope>runtime</scope>
     </dependency>
</dependencies>

示例Excel 檔案包含員工列表,每行代表單個員工的詳細資訊。Excel 檔案中的列直接對應於實體類中的欄位Employee:

  • Name名稱:此列對映到實體name中的欄位Employee。
  • Department部門:此列對映到實體department中的欄位Employee。
  • Email電子郵件:此列對映到實體email中的欄位Employee。

實體Employee類定義如下:

@Entity
public class Employee {
     
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String department;
    private String email;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public String getDepartment() {
        return department;
    }
 
    public void setDepartment(String department) {
        this.department = department;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    @Override
    public String toString() {
        return <font>"Employee{" + "id=" + id + ", name=" + name + ", department=" + department + ", email=" + email + '}';
    }    
}


為實體Employee建立一個儲存庫介面。

@Repository
public interface EmployeeRepository extends JpaRepository{
     
}

使用 PoiItemReader 實現 Excel 閱讀器
Spring Batch Extension 提供PoiItemReader讀取 Excel 檔案的功能。在這裡,我們將配置它來處理員工數

@Configuration
public class PoiReader {
     
    @Value(<font>"${excel.file.path}")
    private String filePath;
 
    @Bean
    public ItemReader<Employee> employeeReader() {
        PoiItemReader<Employee> reader = new PoiItemReader<>();
        reader.setResource(new ClassPathResource(filePath));
        reader.setLinesToSkip(1);
        reader.setRowMapper(new BeanWrapperRowMapper<Employee>() {
            {
                setTargetType(Employee.class);
            }
        });
        reader.setName(
"employeeReader");
        return reader;
    }
     
}

  • @Value註釋從應用程式屬性中注入檔案路徑(例如excel.file.path=employees.xlsx)。
  • PoiItemReader將 Excel 檔案位置配置為 ClasspathResource。
  • BeanWrapperRowMapper自動將列名對映到類中相應的欄位Employee。
  • setLinesToSkip(1)確保跳過標題行。

使用 JpaRepository 儲存到資料庫
接下來,我們將利用 Spring Data JPA EmployeeRepository將處理後的員工資料儲存到資料庫。

@Bean
public ItemWriter employeeWriter(EmployeeRepository repository) {
    return items -> {
        for (Employee employee : items) {
            repository.save(employee);
            System.out.println(<font>"Employee saved: " + employee.getName());
        }
    };
}

該 ItemWriter 會遍歷僱員物件列表,並使用 EmployeeRepository 儲存這些物件。

配置Spring Batch作業
以下是本文中用於配置 Spring Batch 作業的完整示例程式碼,該作業從 Excel 檔案讀取員工資料、處理資料並將其寫入資料庫。每個步驟都分解為更小的元件 - ItemReader、ItemProcessor 和 ItemWriter。

@Configuration
public class PoiReader {
 
    @Value(<font>"${excel.file.path}")
    private String filePath;
 
    private final JobRepository jobRepository;
 
    private final PlatformTransactionManager transactionManager;
 
    private final EmployeeRepository employeeRepository;
 
    public PoiReader(JobRepository jobRepository, EmployeeRepository employeeRepository, PlatformTransactionManager transactionManager) {
        this.jobRepository = jobRepository;
        this.transactionManager = transactionManager;
        this.employeeRepository = employeeRepository;
    }
 
    @Bean
    public ItemReader<Employee> employeeReader() {
        PoiItemReader<Employee> reader = new PoiItemReader<>();
        reader.setResource(new ClassPathResource(filePath));
        reader.setLinesToSkip(1);
        reader.setRowMapper(new BeanWrapperRowMapper<Employee>() {
            {
                setTargetType(Employee.class);
            }
        });
        reader.setName(
"employeeReader");
        return reader;
    }
 
    @Bean
    public ItemWriter<Employee> employeeWriter() {
        return items -> {
            for (Employee employee : items) {
                employeeRepository.save(employee);
                System.out.println(
"Employee saved: " + employee.getName());
            }
        };
    }
 
    @Bean
    public ItemProcessor<Employee, Employee> processor() {
        return employee -> {
           
// Example processor logic<i>
            employee.setName(employee.getName());
            System.out.println(
"Name: " + employee.getName() + ", Department: " + employee.getDepartment());
            return employee;
        };
    }
 
    @Bean
    public Step chunkProcessingStep() {
 
        var builder = new StepBuilder(
"chunkProcessingStep", jobRepository);
        return builder
                .<Employee, Employee>chunk(1, transactionManager)
                .reader(employeeReader())
                .processor(processor())
                .writer(employeeWriter())
                .build();
    }
 
    @Bean
    Job importUserJob(Step step1) {
 
        var builder = new JobBuilder(
"importUserJob", jobRepository);
        return builder
                .incrementer(new RunIdIncrementer())
                .start(step1)
                .build();
    }
 
}


上述程式碼定義了一個 Spring Batch 配置,該配置使用 @Value 注入 Excel 檔案的路徑,還包括 JobRepository、EmployeeRepository 和 PlatformTransactionManager 的建構函式注入,它們分別用於管理作業執行、資料庫操作和事務管理。 該類定義了對批處理至關重要的幾個 Bean:

該類定義了對批處理至關重要的幾個bean:

  • employeeReader(): 此 Bean 使用 PoiItemReader 從 Excel 檔案中讀取資料。 它會跳過標題行,並使用 BeanWrapperRowMapper 將每一行對映到僱員物件:
  • employeeWriter(): 此 bean 使用 EmployeeRepository 將處理過的僱員物件寫入資料庫。
  • Processor(): 此 Bean 處理每個僱員物件。 在此示例中,它會列印僱員的姓名和部門。
  • chunkProcessingStep(): 它將讀取器、處理器和寫入器結合起來,以處理資料塊。
  • importUserJob(): 此 Bean 將定義批處理作業本身,從定義的步驟開始,並使用 RunIdIncrementer 確保作業執行的唯一性。

排程批處理作業
 一旦配置好批處理作業,下一步就是排程它在特定時間間隔執行。 Spring Boot 透過 @EnableScheduling 註解和 @Scheduled 註解提供了排程功能。 透過使用這些註解,我們可以輕鬆地排程批處理作業以固定的時間間隔執行,如每小時、每天或基於更復雜的 cron 表示式。

在我們的示例中,我們將排程批處理作業在每小時的頂部執行。 這樣就能確保每小時從 Excel 檔案讀取僱員資料並寫入資料庫。

@Configuration
@EnableScheduling
public class BatchScheduler {
 
    @Autowired
    private JobLauncher jobLauncher;
 
    @Autowired
    private Job importUserJob;
 
    @Scheduled(cron = <font>"0 0 * * * ?") // Run at the top of every hour<i>
    public void perform() throws Exception {
        jobLauncher.run(importUserJob, new JobParameters());
    }
        
}

上面的程式碼片段排程一個批處理任務在每小時的頂部執行。 該類使用 @EnableScheduling 進行註解,以啟用 Spring 的計劃任務執行功能。 它自動連線了 JobLauncher 和 Job Bean,以啟動和執行名為 importUserJob 的批處理任務。

@Scheduled(cron = "0 0 * * * ?") 註解指定了 perform() 方法應在每小時開始時執行,從而觸發批處理任務。

結論
在本文中,我們探討了如何實現 Spring Batch Excel 閱讀器,以便從 Excel 檔案中高效讀取員工資料並將其儲存到資料庫中。 我們介紹了從設定專案和定義實體到配置批處理作業和使用 cron 表示式排程的各個步驟。
 

相關文章