เรื่องของเรื่อง ก็มาจาก ที่ว่า คุณ roofimon อยากได้ โจทย์มาทำ dojo ครับเลย อยากได้ sample data ใน database ซัก แสน record เลยใช้อำนาจมืด(เอ้ย!)ไหว้วานให้ผมทำ
ก็เลย มานั่งนึกว่าเอ้อ จะเขียนแบบธรรมดาๆ ก็ ดูว่า จะเสียชื่ออาจารย์อย่างคุณ roofimon ก็เลย คิดแบบเมพๆ หน่อยเขียน spring batch ดีกว่า ก็เลย เริ่มกันไปเลย เริ่มกันที่ intro กันก่อนแล้วกัน
ตาม entry เรื่อง Spring Product ของคุณ roofimon เรื่อง spring และลูกๆ spring batch ก็คือหนึ่งในลูกๆ ของ spring ตัวแม่ ในความเห็นผมเอง มองว่า ประโยชน์ของการใช้งาน
spring batch นั่นมีส่วนที่คล้ายกับ spring aop ตัวเก่ง คือ มุ่งหมายจะทำใำห้ระบบทำ logic อันใดอันหนึ่ง โดย design แยกออกมาจาก main business logic โดย spring aop
นั้นมุ่งหมายให้ทำ โดยสามารถกำหนด ว่าจะให้ทำ หลัง, ก่อน หรือ ทั้งก่อนหลัง การกระทำสิ่งใดอีกสิ่งหนึ่ง(main business logic) ทุกครั้งเสมอเฮ้อ เขียนแล้วงง เอง แต่ spring batch ก็ คือ สิ่งที่อยากให้ทำ ในเวลาหรือว่าจังหว่ะที่กำหนด
ที่เจอกันบ่อย ๆ เช่น ทุกวันศุกย์เย็น ต้อง import data from xml อะไรเทือกนั้นเป็นต้น
เกือบจะหมดแรงข้าวต้มละ ยังไมไ่ด้ขึ้นเลย อ่ะจะเข้าละ เด่วจะยืดเยื้อไปกันใหญ่ ผมคิดว่าการที่เราจะเรียนรู้และเข้าใจสิ่งใหม่ที่เราไม่เคยใช้ ง่ายที่สุดก็คือการทำตามตัวอย่าง งั้นก็อย่างที่บอก เรามาลองทำอะไรเล่นๆ ตามธรรมเนียมกัน ไหนๆก็ไหนๆ ก็เลย เขียนต่อ จาก spring66-app แล้วกันเพราะคาดว่าหลายๆ ท่านก็คงทำตามมาตลอด จัดไปครับ แบบผิวๆ บางๆ
ตัวละครของ Spring Batch
เรามาเริ่มกันที่ การแนะนำตัวละครหลักเลยดีกว่ครับ ซึ่งก็คือ ผู้มีบทบาทในการ เริ่มต้นใช้งานมันนั่นเอง มีดังนี้นะครับ ขอแนะนำแบบ ผิวๆ บางๆ นะครับ ขอคนช่วยเสริมเติมแต่งส่วนที่ผมเข้าใจผิดนะครับ
Job ก็คืองานหรือว่า batch นึงที่เราจะสร้างขึ้นครับ โดยในแต่ละงานอาจจะ ประกอบไปด้วยหนึ่งหรือหลายขั้นตอน(Step)ครับ Step ก็ คือขั้นตอนที่อยู่ใน Job นั่นหล่ะครับโดย Job ก็จะมี Instance คือ JobInstance ที่สามารถรับพาามิเตอร์ได้คือ JobParameter โดยในแต่ละการแสดงบทบาทของ JobInstance จะ ถูกเรียกว่า JobExecution เรา ลองนึกภาพเล่นๆ ว่า สมมุติเราจะทำการ นำเข้าข้อมูล users จำนวน นึงแล้วต้องการ ที่จะทำการ truncate ข้อมูลเก่าทิ้งทั้งหมด (อย่าถามนะครับว่าทำ ทำไม บอกแล้วว่าสมมุติ) งานที่เราจะทำก็จะมี สองขั้นตอนคือ หนึ่งทำการ truncate ก่อนแล้วแค่ทำการ insert ข้อมูลใหม่ลงไป พอเห็นภาพมั้ยครับ แต่ spring66 เราจะทำ toturial ทั้งทีใหญ่มหาศาลครับ จะมีงานเดียวได้อย่างไร เราจึงลืมไปไมไ่ด้เลยที่จะแนะนำ สิ่งที่ พอ spring batch เค้าต้องการอย่างแรงคือ JobRepository เป็นตัวเก็บ งานที่เราจะทำั้งหมดไว้ นั่นเองตรงนี้ไม่ค่อยเคลีย ในการอธิบายครับต้องรบกวนท่านอื่นเสริม แต่อ่ะ พอๆ เด่วจะตัวละครเยอะไปกันใหญ่ เอาหล่ะทีนี้ จากกระทู้ก่อนๆ เราก็มีการจัดเตรียมอุปกรณ์การแสดงเพียบ เราจึงไม่ต้องไปทำใหม่ทั้งหมด ขอไปเอาของเค้ามาใช้หน่อยแล้วกันนะครับ
เริ่มกันที่ xml ตัว พ่อครับ pom.xml นั่นเอง ถ้าผ่านมาทั้งหมดก็ น่าจะประมาณแบบนี้เนอะ
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.spring66.tutorial</groupId> <artifactId>spring66-app</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version> <name>spring66-app</name> <url>http://maven.apache.org</url> <build> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>1.5</source> <target>1.5</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <artifactId>maven-resources-plugin</artifactId> <version>2.2</version> <configuration> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>3.8.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> <version>2.5.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>2.5.6</version> <scope>test</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.2</version> </dependency> </dependencies> </project>
เราก็ทำการเพิ่มสิ่งที่เราจะใช้เข้าไปโลดครับ
<dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-core</artifactId> <version>1.1.3.RELEASE-A</version> </dependency> <dependency> <groupId>org.springframework.batch</groupId> <artifactId>spring-batch-infrastructure </artifactId> <version>1.1.3.RELEASE-A</version> </dependency>
เรื่องของ dependency เนี่ย รบกวนต้องตามอ่านกันเอานะครับ เยอะเหลือหลายครับ blog แรกผมครับพลังน้อย
เหมือน อย่างเคยๆ เจ้าพ่อสปริงเค้าก็เขียนอะไรๆ มาให้ใช้เยอะแยะไปหมด เริ่มกันที่ เราจะทำการสร้าง csv file เก็บ รายชื่อของ usrId,usrName,usrPassword นะครับโดยใช้ , เป็นตัวขั้นหน้าตา ก็ประมาณนี้ users.csv
userId,userName,userPassword
id1,Poorprogrammer001,password01
id2,Poorprogrammer002,password02
id3,Poorprogrammer003,password03
id4,Poorprogrammer004,password04
id5,Poorprogrammer005,password05
id6,Poorprogrammer006,password06
ด้วย บุญเก่าทำให้เรามี applicationContext.xml แล้ว ลืมบอกไปว่า ในแต่ละ งานก็จะมีการ cofig ทั้งหมด ใน xml นะครับ อย่างที่ เจ้าพ่อ xml config อย่างสปริงเค้าถนัด แต่เรื่องของการ เขียนเป็น annotation ผมไม่แน่ใจนะครับว่าได้หรือยัง เราก็ เลยขอ config ไปที่เราไปสร้าง bean ที่จะทำการ อ่าน และคัดแยก ตัว file ของเรากัเถอะครับ จะว่าไป มานั่งเขียน คลาส เพื่ออ่าน io file ก็ดูจะถึกไปหน่อย เค้ามีของดีมาให้ใช้ครับ คือ org.springframework.batch.item.file.FlatFileItemReader เอ้อ ฟังชื่อคลาสแล้วดูดีไปดูกันเลย เราก็ไป regis มันลง applicationContext
<bean id="inputFile" class="org.springframework.core.io.ClassPathResource"> <constructor-arg value="/users.csv" /> </bean> <bean id="mapper" class="org.springframework.batch.item.file.mapping.BeanWrapperFieldSetMapper"> <property name="targetType" value="com.spring66.tutorial.model.Users" /> </bean> <bean id="reader" class="org.springframework.batch.item.file.FlatFileItemReader"> <property name="resource" ref="inputFile" /> <property name="firstLineIsHeader" value="true" /> <property name="lineTokenizer"> <bean class="org.springframework.batch.item.file.transform.DelimitedLineTokenizer"> <property name="delimiter" value="," /> </bean> </property> <property name="fieldSetMapper"> <bean class="com.spring66.tutorial.batch.UsersMapper" /> </property> </bean>
เรา ก็ สร้าง reader ขึ้นมา ครับโดยมาดูกันซิว่าเรา ขาดอะไรอย่าเพิง test นะครับเด่วจะแดงแล้วก็โดนด่าเหมือนที่พี่ roofimon เค้าโดนประจำแต่ก็ TDD อ่ะนะ เอาเป็นว่าเรารู็้ทัน
<property name=”resource” ref=”inputFile”/>
ref ไป inputfie เราก็ไปขอความช่วยเหลือจาก org.springframework.core.io.ClassPathResource
<property name=”firstLineIsHeader” value=”true”/>
อันนี้หล่ะผมชอบใจ๊ชอบใจ ถึงมันจะดูเฉพาะงานไปหน่อยแต่ว่า เราก็เจอมันบ่อยเสียจริงๆ
lineTokenizer อันนี้ก็เท่ หล่อมาเลย บอกได้เลยว่าใช้ DelimitedLineTokenizer เป็นอะไรตัวนี้ น่าจะมาจาก String Tokenizer เอง
<property name=”fieldSetMapper”>
<bean class=”com.spring66.tutorial.batch.UsersMapper”/>
</property>
ตัว สุดท้ายคือตัว mapping class คุ้นๆ มั้ยครับคลาสที่ลงท้ายด้วยพวก mapper ทั้งหลาย ก็คือตัวที่จะ map model เ้ข้ากับ flele ที่อ่านมา นั้นหล่ะครับ แต่ด้วยความ ขี้ค้ราน ขั้นมหาศาลของผม เลยขอไปเอา Users model มาแก้นะครับ โดยลบ property อื่นๆ ออกให้เหลือแค่สามตัว เพื่อ map เข้ากับ csv ผม แห่ๆ
โดยเราก็ไปสร้าง คลาส Mapper ขึ้นมาหน้าตา ไม่ค่อยหล่อแบบนี้ครับ
package com.spring66.tutorial.batch;
import com.spring66.tutorial.model.Users;
import org.springframework.batch.item.file.mapping.FieldSet;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
/**
*
* @author Phongsak
*/
public class UsersMapper implements FieldSetMapper {
public Object mapLine(FieldSet fs) {
Users user = new Users();
int idx = 0;
user.setUsrId( fs.readString(idx++) );
user.setUsrName( fs.readString(idx++) );
user.setUsrPwd( fs.readString(idx++) );
System.out.println("User is = " + user.toString());
return user;
}
}
สังเกตุว่า มันทำการ implements FieldSetMapper นะครับ
เพิ่ง สังเกตุอีกทีว่า โอ้ว นี่ มัน เขียนมาเยอะแ้ล้วนี่นา เลยขอ publish ก่อนนะครับ เดี๋ยวมาต่อ ภาค ต่อไป หมดแรงครับ ดองมานานแล้วด้วย อาจจะจบห้วนไปนิดนึงนะครับ แต่ เพื่อปลุกกระแสๆ อิอิ
blog แรกผมเลยนะครับผิดพลาดประการใดขออภัยครับ แนะนำผมด้วยนะครับ ขอบคุณครับ
