เรามาเขียน @Annotation กันเถอะ
วันนี้ผมมีงานที่ต้องนำข้อมูล Excel file ลง Database
ลักษณะงานแบบนี้หลายคนคงเจอกันบ่อยมากมาย และหลายคนก็คงเขียนกันแบบถึก ๆ
... เสียใจด้วย ที่ java code ที่ท่านเห็นต่อไปนี้ จะเป็น version 7
... #ร้องให้หนักมาก
วันนี้เรามาทำให้มันยากกว่าเดิมกันดีกว่า ... จะทำไปทำไม
เปลี่ยนเป็นวันนี้เรามาทำอะไรกันเล่น ๆ สนุก ๆ กันดีกว่า
ลักษณะ การทำงาน ก็คงคล้าย ๆ กับ การทำ Mapping ... ใครที่เคยเขียน
Hibernate Annotation Mapping คงคุ้นเคยกันดีว่า เราสามารถใช้ความ
สามารถของเจ้า @Annotation นั้น ทำการ Map Field ใน Entity Class
ให้ตรงกับ Field ใน Database ... แต่อันนั้นมันฉลาดกว่าที่ผมทำอะ -_-"
เริ่มเลยดีกว่า เรามาทำหนด Class ที่เป็น Entity ที่ต้องการนำข้อมูลจากเจ้า
Excel File มายัดลงกันเลย
package x.y.z.util.excelupload;
import x.y.z.util.excelupload.annotation.ExcelField;
import x.t.z.util.excelupload.type.ExcelFieldType;
public class TestBean {
@ExcelField(type = ExcelFieldType.STRING, position = 0)
private String pid;
@ExcelField(type = ExcelFieldType.STRING, position = 1)
private String name;
@ExcelField(type = ExcelFieldType.STRING, position = 2)
private String surname;
public TestBean(){
}
// get - set ...
public String getPid() {
return pid;
}
//...
}
เราทำการกำหนด class มาตัวนึง ใช้เก็บข้อมูล pid, name, surname
ก็ประมาณว่า เลขที่บัตรประจำตัวประชาชน, ชื่อ, นามสกุล อะไรประมาณนี้
อย่าลืมเขียน get - set ให้ครบนะครับ แต่หลัก ๆ เราจะใช้ set เพื่อจะ
ใช้ในการบันทึกข้อมูล
จากด้นบน เราจะเห็นว่ามี @ExcelField ดูหน้าตาแปลก ๆ ไปนิดถ้าชอบเดี๋ยวก็ชิน
เรามาดูข้างใน @Annotation ตัวนี้กันเลยว่ามันมีหน้าตาเป็นแบบใหน
package x.y.z.util.excelupload.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import x.y.z.util.excelupload.type.ExcelFieldType;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {
public int position() default 0;
public ExcelFieldType type() default ExcelFieldType.STRING;
public String format() default "";
}
จาก code หลัก ๆ ผมก็จำลองกำหนดแค่
- position คือตำแหน่ง cell ใน excelfile
- type คือรูปแบบข้อมูล เป็นตัวอักษร, ตัวเลข, วัน(แต่ตรงนี้เราคงไม่ได้กำหนดหมด)
- format อันนี้ผมทำเผื่อไว้ กรณีต้องกำหนดข้อมูลวันที่เข้ามามีรูปแบบ วันที่จาก excel
file หรือจะเป็นรูปแบบตัวเลข
มีการประกาศ enum ไว้ในส่วน type เพื่อกันการเลือกประเภทข้อมูลผิด
package x.y.z.util.excelupload.type;
public enum ExcelFieldType {
STRING, DATE, INTEGER, BIGDECIMAL
}
ก็มีแค่นี้แหละ enum จัดไปซะตั้ง 1 file เปลืองจริง
เอาหละ แล้วเรามาดูในส่วน util ที่เราสร้างมาเอาฮา เอ้ย มาทำงานกัน
package x.y.z.util.excelupload;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import x.y.z.util.excelupload.annotation.ExcelField;
import x.y.z.util.excelupload.type.ExcelFieldType;
public class ExcelToBeanUtil {
private Class clazz;
private List dataList;
private int rowAt = 0;
public ExcelToBeanUtil(Class clazz) {
this.clazz = clazz;
dataList = new ArrayList<>();
}
private void addDataTolist(T data) {
if (null == dataList) {
dataList = new ArrayList<>();
}
dataList.add(data);
}
public ExcelToBeanUtil startRowAt(int rowAt) {
this.rowAt = rowAt;
return this;
}
public List execute(String pathFile) throws
InvalidFormatException, IOException,
InstantiationException, IllegalAccessException {
FileInputStream file = new FileInputStream(
new File(pathFile));
XSSFWorkbook workbook = new XSSFWorkbook(file);
XSSFSheet sheet = workbook.getSheetAt(0);
Iterator rowIterator = sheet.iterator();
for (int i = 0; rowIterator.hasNext() && i < rowAt; i++) {
rowIterator.next();
}
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
if (null == row.getCell(0).toString()
|| row.getCell(0).getStringCellValue().equalsIgnoreCase("")) {
break;
}
T data = clazz.newInstance();
Field[] fields = data.getClass().getDeclaredFields();
for (Field f : fields) {
if (null == f.getAnnotations() || 0 == f.getAnnotations().length) {
continue;
}
ExcelField exField = f.getAnnotation(ExcelField.class);
String fName = f.getName();
fName = "set"
+ fName.substring(0, 1).toUpperCase()
+ fName.substring(1, fName.length());
try {
Method method;
if (exField.type().equals(ExcelFieldType.INTEGER)) {
method = data.getClass()
.getDeclaredMethod(fName, Integer.class);
method.invoke(data, Integer.parseInt(
row.getCell(exField.position()).toString()));
} else if (exField.type().equals(ExcelFieldType.BIGDECIMAL)) {
method = data.getClass()
.getDeclaredMethod(fName, BigDecimal.class);
BigDecimal b = new BigDecimal(
row.getCell(exField.position()).toString());
method.invoke(data, b);
} else {
method = data.getClass()
.getDeclaredMethod(fName, String.class);
method.invoke(data,
row.getCell(exField.position()).toString());
}
} catch (Exception ex) {
Logger.getLogger(ExcelToBeanUtil.class.getName())
.log(Level.SEVERE, null, ex);
}
}
addDataTolist(data);
}
return this.dataList;
}
}
เอาหละ ทำไมมันได้ยาวแบบนี้ ... อย่าคิดมาก copy มาล้วน ๆ เดียวว่าง ๆ
ค่อยมาปรับแต่งให้ดูดี
การทำงานของตัว util นี้ไม่มีอะไรมาก มันแค่ไปหาว่า @Annotation ที่เราสร้าง
ขึ้นนั้น ไปประกาศบน field ใดบ้าง หลังจากนั้น มันก็จะเอา field ไป เพิ่ม .set
เพื่อทำการบันทึกข้อมูลให้ field นั้น ... เอ่อ อย่าคิดมาก
วิธีการดึงข้อมูลก็ใช้แบบเรียกตรงไปเลย
อีกส่วนเป็นการกำหนด ให้เราเริ่มอ่าน excel ได้ตั้งแต่บนนทัดใหน startRowAt อันนี้
ผมลักไก่ไปก่อน ha ใช้วิธีวนทิ้งตามจำนวน
เอาเป็นว่า เรามาเขียน test กันดีกว่า ว่ามันใช้งานได้จริงๆนะ
เรามาเริ่มดูข้อมูลที่สมมติขึ้นมาก่อน
| Pid | Name | Surname |
| 3777777777777 | kan | swatchupong |
| 4888888888888 | ดรีม | ทีม |
| 4888888888889 | ซูเปอร์ | ทีม |
Code Test ก็จะประมาณนี้ (อันนี้ gen test file จาก netbean
นะครับเขียนเองก็ได้)
package x.y.z.util.excelupload;
import java.util.List;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class ExcelToBeanUtilTest {
public ExcelToBeanUtilTest() {
}
@BeforeClass
public static void setUpClass() {
}
@AfterClass
public static void tearDownClass() {
}
@Before
public void setUp() {
}
@After
public void tearDown() {
}
@Test
public void testExecute() throws Exception {
System.out.println("execute");
ExcelToBeanUtil instance = new ExcelToBeanUtil(TestBean.class)
.startRowAt(1);
List beanList = instance
.execute("/Users/kanswa/Desktop/profiletest.xlsx");
for (TestBean bean : beanList) {
System.out.println(" RESULT : "
+ ToStringBuilder.reflectionToString(
bean, ToStringStyle.MULTI_LINE_STYLE
)
);
}
}
}
ผลการ run ถ้าไม่มีอะไรนอกเหนือจากนี้นะจะเป็นดังนี้
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Running z.y.z.util.excelupload.ExcelToBeanUtilTest
execute
RESULT : x.y.z.util.excelupload.TestBean@66b697de[
pid=3777777777777
name=kan
surname=swatchupong
]
RESULT : x.y.z.util.excelupload.TestBean@7163ff8b[
pid=4888888888888
name=ดรีม
surname=ทีม
]
RESULT : x.y.z.util.excelupload.TestBean@5c1e38d5[
pid=4888888888889
name=ซูเปอร์
surname=ทีม
]
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 5.525 sec
จาก code test นี้จะเห็นว่า เวลาเขียนใช้งาน เราก็แค่โยน Entity ที่เรากำหนด
ประเภทจาก @ExcelField แล้วเข้าไป ก่อนทำการเลือก file ที่ต้องการอ่าน
ข้อดีของการเขียนแบบนี้คือ เมื่อเรามีการเพิ่ม field เพื่อรับข้อมูลจาก Excel file
ที่มีการเปลี่ยนแปลง ก็จะสามารถทำได้โดยงาน
ข้อเสียคือ ... ขี้เกียจอธิบายให้คนอื่นเข้าใจ -_-"
ลงท้ายนี้ ประเภทข้อมูลผมยังเขียนไม่หมดนะแต่หวังว่าคงจะพอเห็นแนวทางเผื่อจะ
ลองนำไปปรับใช้งานก็ปรับแต่งได้ตามใจชอบเลยครับ
เอาจริง ๆ คือ entity ที่ผมกำหนดนี้ก็เดี๋ยวใส่ @Annotation ของ Hibernate
แล้วจับ save เลยเนี่ยแหละครับ
วันนี้หมดแรงและ หวังว่าจะเป็น ประโยชน์ต่อผู้ใดก็ตามไม่มาก ก็น้อยที่ที่จะมาเขียนฮา ๆ กันนะครับ
ขอบคุณครับ ^^