Batch input là gì
Xin chào các bạn, hôm nay mình xin được chia sẻ kinh nghiệm nhập môn về Spring Batch. Tham khảo : https://spring.io/guides/gs/batch-processing/ Show
Mình mới bắt đầu tìm hiểu về tạo Batch dùng Spring Framework gần đây. Mình viết blog này để củng cố lại những gì đã học cũng như hi vọng có ích với các bạn cũng cùng mong muốn tìm hiểu. 1.Bài toánTa giả sử bài toán là ta có một cửa hàng bán đồ điện tử, hàng ngày các order được ghi lại và log dưới dạng csv bởi nhân viên cửa hàng. Tuy nhiên hệ thống chưa có chức năng để nhập liệu các csv này vào database, cần một batch chạy mỗi cuối ngày để làm công việc này. 1.1.(Input) Nội dung csv orderorders.csv customer_id,item_id,item_name,item_price,purchase_date 1,1,10000000,asus notebook,2020/07/16 8:00:00 1,2,12000000,macbook pro 13 inch,2020/07/16 9:00:00 2,2,12000000,macbook pro 13 inch,2020/07/16 10:00:00 2,3,15000000,macbook pro 15 inch,2020/07/17 10:00:00Nội dung file này thể hiện từng customer đã mua từng item với giá bao nhiêu vào ngày nào. Ví dụ dòng 1 thể hiện ý nghĩa là customer số 1 đã mua item số 1 tên là asus notebook vào thời điểm 2020/07/16 8:00 với giá 10.000.000 triệu. 1.2.(Ouput) Nội dung dữ liệu thống kêTa lưu dữ liệu thống kê dưới dạng một bảng SQL, và ta mong muốn dữ liệu ứng với orders.csv sẽ trông như sau :
Nội dung bảng này thể hiện từng item đã được mua tổng số bao nhiêu cái theo từng ngày. Và công việc của ta là tạo ra batch để tạo ra output tương ứng với input trên. Note : Để thực hiện nghiệp vụ trên, không nhất thiết ta phải dùng batch mà có thể dùng chức năng web. Tuy nhiên với mục đích là trải nghiệm Spring Batch nên ta giả sử với điều kiện đặc thù là ta cần dùng batch. 2.Chuẩn bịMôi trường trên máy host của mình như sau, các bạn có thể tham khảo phù hợp cho máy của mình
Mình cũng đã dùng thử trên VS Code mà không thấy tính năng tự động import (Import Organization) không hiệu quả lắm nên chuyển sang Eclipse. Về IDE thì dùng IntellJ có lẽ rất tốt nhưng bản Community không hỗ trợ Spring nên mình thôi. 3.ImplementMình sẽ dùng Spring Initialzr để tạo boilerplate cho project. https://start.spring.io/ Các lựa chọn
Xong ta chọn Generate, file nén project là demo.zip sẽ được download về. Về local, mình unzip và đặt vào directory tương ứng, đặt con trỏ thao tác terminal ở đây. 3.2. Tạo database Mysql bằng (Docker)Batch sẽ trỏ đến database bằng Mysql nên mình sẽ tạo database này, mình chọn Docker để chứa database này. Nếu các bạn đã có mysql trên máy host thì có thể bỏ qua bước này. 3.2.1.Tạo docker-compose.ymlCấu trúc database docker-compose.yml là file cấu trúc môi trường docker. Mình tạo mới ở trong project với nội dung như sau : demo/docker-compose.yml version: '3' services: database: image: mysql:8.0 volumes: - ./db/dbdata:/var/lib/mysql command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci','--default-authentication-plugin=mysql_native_password'] environment: MYSQL_DATABASE: demo MYSQL_ROOT_PASSWORD: root MYSQL_USER: demo MYSQL_PASSWORD: demo ports: - "33061:3306"Trong đó thông tin cơ bản để access từ máy host sẽ là :
Cấu trúc thư mục : demo/ ... docker-compose.yml db/ ...3.2.2.Khởi tạo các bảngHiện tại vẫn chưa có database hay bảng nào, batch gọi đến sẽ không thực hiện được query nào nên mình tạo dữ liệu ban đầu cần thiết Access từ máy host : $ mysql -u demo -P 33061 -p Enter Password:Tạo bảng cần thiết : CREATE TABLE orders ( id INT AUTO_INCREMENT PRIMARY KEY, customer_id INT(12) NOT NULL, item_id INT(12) NOT NULL, item_name VARCHAR(50), item_price INT(12) NOT NULL, purchase_date DATETIME ); Query OK, 0 rows affected, 3 warnings (0.05 sec)Vậy ta đã chuẩn bị database xong. Tiếp đến là phần chính tạo Batch. 3.3.Import projectỞ 3.1 ta đã tạo project tên demo, tiếp theo ta sẽ dùng eclipse import vào dạng Maven Project. Import > Maven > Existing Maven Project. Vậy là ta có import xong, tiếp theo ta sẽ viết source code cho chương trình. 3.4.Thiết kếChương trình ta xây dựng sẽ có kiến trúc như sau : Ý nghĩa các thành phần
3.5.Viết xử lý3.5.1.Tạo EntityTa sẽ tạo một class để lưu từng từng record csv, đặt tên là Order.class Class này chỉ nêu các trường ứng với từng column trong csv cũng như trong table orders. Các phương thức chỉ có setter và getter. com.example.demo.batchprocessing.Order.java package com.example.demo.batchprocessing; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="orders") public class Order { @Id @GeneratedValue(strategy=GenerationType.AUTO) private Integer id; private Integer customerId; private Integer itemId; private String itemName; private Integer itemPrice; private String purchaseDate; .... // Setter + Getter ... }Thêm annotation @Entity đánh dấu cho Spring biết đây là một Entity thể hiện tương ứng với một table Vì bảng mình tạo đặt tên theo kiểu số nhiều nên mình cần thêm chỉ định name="orders" nữa 3.5.2.Tạo RepositoryRepository là cấu trúc thực hiện lưu entity vào DB trong Spring, không có Repository ta sẽ không thể thực hiện các thao tác lên Entity phản ánh lên DB. com.example.demo.batchprocessing.OrderRepository package com.example.demo.batchprocessing; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @Repository public interface OrderRepository extends CrudRepositoryCrudRepository là interface khi khởi tạo nhận 2 tham số, tham số đầu là Entity nên ta đặt là Order, tham số thứ 2 là kiểu dữ liệu khoá chính (id), ở đây kiểu của orders.id là Integer nên ta đặt là Integer. Ta cũng đánh dấu @Repository để Spring biết đây là một Repository 3.5.3.Tạo BatchConfigurationcom.example.demo.batchprocessing.BatchConfiguration package com.example.demo.batchprocessing; import .... @Configuration @EnableBatchProcessing public class BatchConfiguration { @Autowired public JobBuilderFactory jobBuilderFactory; @Autowired public StepBuilderFactory stepBuilderFactory; @Autowired private OrderRepository orderRepository; @Bean public ItemReader
Tiếp theo ta sẽ tạo job duy nhất cho chương trình và step duy nhất cho job này. @Bean public Job importOrderJob() { return jobBuilderFactory.get("importOrderJob") .incrementer(new RunIdIncrementer()) .listener(new JobCompletionNotificationListener()) .flow(step1()) .end() .build(); } @Bean public Step step1() { return stepBuilderFactory.get("step1") .
3.5.4.Tạo JobCompletionNotificationListenerTa sẽ tạo 1 listener lắng nghe xem khi nào job được thực hiện xong. com.example.demo.batchprocessing.JobCompletionNotificationListener package com.example.demo.batchprocessing; import .... @Component public class JobCompletionNotificationListener extends JobExecutionListenerSupport { private static final Logger log = LoggerFactory.getLogger(JobCompletionNotificationListener.class); @Override public void afterJob(JobExecution jobExecution) { if (jobExecution.getStatus() == BatchStatus.COMPLETED) { log.info("!!! JOB FINISHED "); } } }Listener mình tạo chỉ có chức năng log ra là Job đã chạy xong "!!! FINISHED", do đó ta chỉ khai báo Logger và implement method afterJob() là được. 3.5.5.Tạo BatchProcessingApplicationCác thành phần chính của chương trình đã tạo xong, ta sẽ tạo EntryPoint chính của chương trình. Thông thường ta sẽ cần khai báo batch config ở đâu, gọi run batch với config như vậy v.v... nhưng nhờ có vào đánh dấu annotation @SpringBootApplication Spring cung cấp, những công việc như vậy đã được giản lược. Spring tự discover được configure của batch nằm trong cùng cấu trúc package. Ta chỉ cần khai báo chạy chương trình SpringApplication.run(BatchProcessingApplication.class, args)) là đủ. 3.5.6.Bỏ các class, xử lý mặc địnhKhi tạo thành từ template mẫu từ Spring Initatilizr trong chương trình có sẵn EntryPoint và test class tương ứng cho nó. Nếu giữ như vậy khi chạy thì batch sẽ báo lỗi không chọn được EntryPoint tương ứng nên ta sẽ điều chỉnh xoá EntryPoint mặc định này đi. $ rm src/main/java/com/example/demo/DemoApplication.java $ rm src/main/java/com/example/demo/DemoApplicationTests.javaTa tạo mới Unit Test cho chương trình đơn giản: com.example.demo.batchprocessing.test.DemoApplicationTests.java package com.example.demo.batchprocessing.test; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class BatchProcessingTests { @Test void contextLoads() { } }Viết xử lý đến đây là xong! 4.Chạy chương trình4.1.Khởi động databaseTa khởi động database bằng docker $ docker-compose up -d4.2.Chạy batch qua dòng lệnhTa có thể chạy qua dòng lệnh hay qua editor cũng không vấn đề gì. Qua dòng lệnh thì mình chạy như sau : $ ./mvnw spring-boot:runLần đầu chạy có thể sẽ tốn thời gian để tải các thư viện liên quan về nhưng từ lần thứ 2 trở đi chạy sẽ rất nhanh. Kết quả chạy của mình như hình dưới đây: Kiểm tra trong database xem các order đã được tạo chưa. $ mysql -h 0.0.0.0 -u demo -P 33061 -p demo $ mysql> select * from orders; +----+-------------+---------+---------------------+------------+---------------------+ | id | customer_id | item_id | item_name | item_price | purchase_date | +----+-------------+---------+---------------------+------------+---------------------+ | 85 | 1 | 1 | asus notebook | 10000000 | 2020-07-16 08:00:00 | | 86 | 1 | 2 | macbook pro 13 inch | 12000000 | 2020-07-16 09:00:00 | | 87 | 2 | 2 | macbook pro 13 inch | 12000000 | 2020-07-16 10:00:00 | | 88 | 2 | 3 | macbook pro 15 inch | 15000000 | 2020-07-17 10:00:00 | +----+-------------+---------+---------------------+------------+---------------------+ 4 rows in set (0.00 sec)Các record được tạo giống như nội dung csv đã nêu. Như vậy ta hiểu batch thực hiện công việc OK. 5.KếtQua bài viết này mình đã chia sẻ đến mọi người cách mình thực hiện 1 chương trình batch đơn giản như trên, đọc file csv và insert vào database, hi vọng có ích cho cho các bạn nhập môn batch spring boot. Có thể trong nội dung bài viết mình chưa nêu hết các nội dung cần thiết để chạy chương trình, các bạn có thể tham khảo qua repository sau. https://github.com/mytv1/sample-spring-batch Bài viết có thể còn nhiều thiếu sót, hi vọng nhận được góp ý từ các bạn. Hết. 6.Tham khảohttps://spring.io/guides/gs/batch-processing https://spring.io/guides/gs/accessing-data-mysql |