1. 程式人生 > >Java使用jxl的api實現excel動態資料驗證及匯入匯出

Java使用jxl的api實現excel動態資料驗證及匯入匯出

首先從優缺點上來說

一、jxl

優點:

Jxl對中文支援非常好,操作簡單,方法看名知意。
Jxl是純javaAPI,在跨平臺上表現的非常完美,程式碼可以再windows或者Linux上執行而無需重新編寫
支援Excel 95-2000的所有版本(網上說目前可以支援Excel2007了,還沒有嘗試過)
生成Excel 2000標準格式
支援字型、數字、日期操作
能夠修飾單元格屬性
支援影象和圖表,但是這套API對圖形和圖表的支援很有限,而且僅僅識別PNG格式。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

缺點:效率低,圖片支援不完善,對格式的支援不如POI強大

二、POI

優點:

效率高(資料來源:http://blog.csdn.net/jarvis_java/article/details/4924099)
支援公式,巨集,一些企業應用上會非常實用
能夠修飾單元格屬性
支援字型、數字、日期操作
  • 1
  • 2
  • 3
  • 4
  • 5

缺點:不成熟,程式碼不能跨平臺,貌似不少同行在使用工程中還碰到讓人鬱悶的BUG(最近的專案中也是遇到了一些bug,不過目前沒有查出來是程式碼的問題還是POI的問題,總之問題很詭異,資料替代引數總有失敗的。關於不能跨平臺這一說,我也沒有試驗過,不過Java不是跨平臺嗎?POI是JAVA的一個元件,怎麼就不能跨平臺了呢,總之這些問題還需要在以後的專案中多多實踐,才能比較出區別之處。)

適於以上的比較,如果我們可以使用jxl-api簡單實現poi的某些功能,如自定義註解,通過反射原理動態驗證excel表的資料,匯入及匯出excel檔案。那麼使用jxl-api還是十分高效的。

1.定義註解類,如金額可以使用BigDecimal,日期使用Date,數量使用Integer...,我建立了5個註解類


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelBigDecimal {
int scale() default 2;
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelBoolean {
ExcelEnum False();
ExcelEnum True();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelDate {
String format();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelValid {
String regexp();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelField {
String name();
int sort();
boolean nullable() default true;
}
通過這些註解類,我們就可驗證、排序excel的內容了

2.建立excel表的及欄位註解的實體類


布林類欄位輸出到excel的內容使用列舉型

public enum ExcelEnum {

TRUE("是"), FALSE("否");

public  String name;

ExcelEnum(String name) {
this.name = name;
}

@Override
public String toString() {
return this.name;
}

}

把exccel的實體類欄位的註解存放到一個自定義類

public class ExcelHelper implements Serializable {

private String name;
private String fieldName;
private int sort;
private boolean nullable;
private String regexp;
private String format;
private String falseName;
private String trueName;
private Integer scale;
private Class<?> clazz;
}

這個就是跟excel表內容對於的實體類,sort是輸出excel表時欄位的排序,對於某些欄位可以用正則表示式去驗證

public class OrderForm implements Serializable {

@ExcelField(name = "訂單號", sort = 0, nullable = false)
@ExcelValid(regexp = "\\d{10}$")
private String orderNo;

@ExcelField(name = "姓名", sort = 1, nullable = false)
private String customerName;

@ExcelField(name = "支付金額", sort = 2, nullable = false)
@ExcelValid(regexp = "^[+-]?[0-9]+(.[0-9]{1,2})?$")
private BigDecimal orderAmt;

@ExcelField(name = "優惠金額", sort = 3, nullable = false)
@ExcelBigDecimal(scale = 2)
private BigDecimal discountAmt;

@ExcelField(name = "日期", sort = 4)
@ExcelDate(format = "yyyy/MM/dd HH:mm")
private Date createDate;

@ExcelField(name = "會員", sort = 5, nullable = false)
@ExcelBoolean(False = ExcelEnum.FALSE, True = ExcelEnum.TRUE)
private Boolean isMember;
}

3.寫一個動態方法驗證、excel內容轉換成實體類及實體類轉換成excel格式的方法


Validator是驗證功能的實體類,ExcelUtil是一個動態反射及轉換的實體類

public class Validator {

public static boolean isEffective(String paramString) {
return (paramString != null) && (!"".equals(paramString)) && (!" ".equals(paramString))
&& (!"null".equals(paramString)) && (!"\n".equals(paramString));
}

public static boolean IsNumber(String paramString) {
if (paramString == null)
return false;
return match("-?[0-9]*$", paramString);
}

public static boolean isValidDate(String str, String formatStr) {
boolean convertSuccess = true;
// 指定日期格式為四位年/兩位月份/兩位日期,注意yyyy/MM/dd區分大小寫;
SimpleDateFormat format = new SimpleDateFormat(formatStr);
try {
// 設定lenient為false.
// 否則SimpleDateFormat會比較寬鬆地驗證日期,比如2007/02/29會被接受,並轉換成2007/03/01
format.setLenient(false);
format.parse(str);
} catch (ParseException e) {
// e.printStackTrace();
// 如果throw java.text.ParseException或者NullPointerException,就說明格式不對
convertSuccess = false;
}
return convertSuccess;
}

public static boolean match(String paramString1, String paramString2) {
if (paramString2 == null) {
return false;
}
return Pattern.compile(paramString1).matcher(paramString2).matches();
}
}

public class ExcelUtil {


public static ExcelUtil excelUitl = null;


public static ExcelUtil getInstance() {
if (excelUitl == null) {
excelUitl = new ExcelUtil();
}
return excelUitl;
}


/**
* 讀取註解值、欄位名及欄位的型別

* @param className
* @return
* @throws Exception
*/


public Map<Integer, ExcelHelper> loadExcelAnnotationFieldVlaue(Class<?> className) throws Exception {
Map<String, ExcelHelper> temp = new HashMap<String, ExcelHelper>();
Field[] fields = className.getDeclaredFields();
for (Field field : fields) {
if (!field.getName().equals("serialVersionUID")) {
if (!field.isAccessible())
field.setAccessible(true);


ExcelHelper helper = new ExcelHelper();
Type type = field.getGenericType();
if (type instanceof Class<?>) {
Class<?> cls = (Class<?>) type;
helper.setClazz(cls);
}
helper.setFieldName(field.getName());
temp.put(field.getName(), helper);
Annotation[] ans = field.getAnnotations();
for (Annotation annotation : ans) {
if (annotation.annotationType().equals(ExcelField.class)) {
ExcelField fd = field.getAnnotation(ExcelField.class);
temp.get(field.getName()).setSort(fd.sort());
temp.get(field.getName()).setName(fd.name());
temp.get(field.getName()).setNullable(fd.nullable());
} else if (annotation.annotationType().equals(ExcelBoolean.class)) {
ExcelBoolean fd = field.getAnnotation(ExcelBoolean.class);
temp.get(field.getName()).setFalseName(fd.False().toString());
temp.get(field.getName()).setTrueName(fd.True().toString());
} else if (annotation.annotationType().equals(ExcelDate.class)) {
ExcelDate fd = field.getAnnotation(ExcelDate.class);
temp.get(field.getName()).setFormat(fd.format());
} else if (annotation.annotationType().equals(ExcelValid.class)) {
ExcelValid fd = field.getAnnotation(ExcelValid.class);
temp.get(field.getName()).setRegexp(fd.regexp());
} else if (annotation.annotationType().equals(ExcelBigDecimal.class)) {
ExcelBigDecimal fd = field.getAnnotation(ExcelBigDecimal.class);
temp.get(field.getName()).setScale(fd.scale());
}
}
}
}


Map<Integer, ExcelHelper> map = new HashMap<>();
for (Map.Entry<String, ExcelHelper> m : temp.entrySet()) {
map.put(m.getValue().getSort(), m.getValue());
}


return map;
}


/**
* 獲取Excel顯示的中文名及排列的順序

* @param className
* @return
*/
public Map<Integer, String> getExcelFieldName(Class<?> className) {
Map<Integer, String> map = new HashMap<Integer, String>();
Field[] fields = className.getDeclaredFields();
for (Field field : fields) {
if (!field.getName().equals("serialVersionUID")) {


if (field.isAnnotationPresent(ExcelField.class)) {
ExcelField fd = field.getAnnotation(ExcelField.class);
if (!field.isAccessible())
field.setAccessible(true);
map.put(fd.sort(), fd.name());
}
}
}
return map;
}


/**
* 比較Excel的頭欄位與實體類的showname數量、名稱及順序是否一致

* @param sheet
* @param map
* @return
*/
public boolean equalsArrays(Sheet sheet, Map<Integer, String> map) {
boolean check = true;
for (int k = 0; k < sheet.getColumns(); k++) {
if (!sheet.getCell(k, 0).getContents().equals(map.get(k))) {
check = false;
break;
}
}
return check;
}


/**
* 校驗實體類與Excel的欄位是否是相同型別

* @param sheet
* @param clazz
* @return
* @throws Exception
*/
public String checkExcelContent(Sheet sheet, Class<?> clazz) throws Exception {
StringBuilder result = new StringBuilder();
result.append("");
int size = sheet.getRows();
Cell[] heads = sheet.getRow(0);
Map<Integer, ExcelHelper> map = loadExcelAnnotationFieldVlaue(clazz);
for (int i = 1; i < size; i++) {
Cell[] cells = sheet.getRow(i);
int len = cells.length;
for (int j = 0; j < len; j++) {
boolean warnning = false;
ExcelHelper helper = map.get(j);
// 判斷欄位內容是否為非空欄位
if (!helper.isNullable()) {
if (!Validator.isEffective(cells[j].getContents())) {
warnning = true;
}
}


if (!warnning) {
// 判斷欄位註解是否存在規則過濾
if (Validator.isEffective(cells[j].getContents())) {


if (Validator.isEffective(helper.getRegexp())) {
if (!Validator.match(helper.getRegexp(), cells[j].getContents())) {
warnning = true;
}
}
}
}


if (!warnning) {
if (Date.class.isAssignableFrom(helper.getClazz())) {
if (Validator.isEffective(cells[j].getContents())) {
if (!Validator.isValidDate(cells[j].getContents(), helper.getFormat())) {
warnning = true;
}
}
} else if (Boolean.class.isAssignableFrom(helper.getClazz())) {


if (!(cells[j].getContents().equals(helper.getFalseName())
|| cells[j].getContents().equals(helper.getTrueName()))) {
warnning = true;
}
} else if (Integer.class.isAssignableFrom(helper.getClazz())) {
if (!Validator.IsNumber(cells[j].getContents())) {
warnning = true;
}
} else if (BigDecimal.class.isAssignableFrom(helper.getClazz())) {
String regexp = "^[+-]?[0-9]+(.[0-9]{1," + (helper.getScale() != null ? helper.getScale() : 2)
+ "})?$";
if (!(Validator.match(regexp, cells[j].getContents()))) {
warnning = true;
}
}


}


if (warnning) {
if (result.toString().indexOf(heads[j].getContents()) == -1) {
result.append("[" + heads[j].getContents() + "]").append(",");
}
}
}
}
return result.toString();
}


/**
* 將Excel的內容轉換成實體類

* @param sheet
* @param clazz
* @return
* @throws Exception
*/
public <T> List<T> importExcelToEntity(Sheet sheet, Class<T> clazz) throws Exception {
List<T> list = new ArrayList<>();
Map<Integer, ExcelHelper> map = loadExcelAnnotationFieldVlaue(clazz);
int size = sheet.getRows();
for (int i = 1; i < size; i++) {
Cell[] cells = sheet.getRow(i);
int len = cells.length;
T t = (T) clazz.newInstance();
for (int j = 0; j < len; j++) {
ExcelHelper helper = map.get(j);
Field f = t.getClass().getDeclaredField(helper.getFieldName());
if (!f.isAccessible())
f.setAccessible(true);
if (Date.class.isAssignableFrom(helper.getClazz())) {
if (Validator.isEffective(cells[j].getContents())) {
f.set(t, new SimpleDateFormat(helper.getFormat()).parse(cells[j].getContents().toString()));
} else {
f.set(t, null);
}
} else if (BigDecimal.class.isAssignableFrom(helper.getClazz())) {
f.set(t, BigDecimal.valueOf(Double.valueOf(cells[j].getContents().toString()))
.setScale(helper.getScale() != null ? helper.getScale() : 2, BigDecimal.ROUND_HALF_UP));
} else if (Boolean.class.isAssignableFrom(helper.getClazz())) {
f.set(t, cells[j].getContents().toString().equals(helper.getTrueName()) ? true : false);
} else if (String.class.isAssignableFrom(helper.getClazz())) {
f.set(t, cells[j].getContents().toString());
} else if (Integer.class.isAssignableFrom(helper.getClazz())) {
f.set(t, Integer.valueOf(cells[j].getContents().toString()));
}
}
list.add(t);
}
return list;
}


/**
* 將實體類轉換成List<Map>格式以便輸出到Excel

* @param list
* @return
* @throws Exception
*/
public <T> List<Map<Integer, String>> exportExcel(List<T> list) throws Exception {
Class<?> cls = list.get(0).getClass();
List<Map<Integer, String>> data = new ArrayList<>();
Map<Integer, ExcelHelper> helper = loadExcelAnnotationFieldVlaue(cls);
// 存放Excel檔案頭部的欄位中文名
Map<Integer, String> title = new HashMap<>();
Set<Integer> set = helper.keySet();
for (Integer key : set) {
title.put(key, helper.get(key).getName());
}
data.add(title);


// 存放Excel的內容
for (T l : list) {
Map<Integer, String> contentMap = new HashMap<>();
for (int i = 0; i < helper.size(); i++) {
ExcelHelper excelHelper = helper.get(i);
Field f = cls.getDeclaredField(excelHelper.getFieldName());
if (!f.isAccessible())
f.setAccessible(true);
if (String.class.isAssignableFrom(excelHelper.getClazz())) {
contentMap.put(i, f.get(l) != null ? String.valueOf(f.get(l)) : "");
} else if (Date.class.isAssignableFrom(excelHelper.getClazz())) {
contentMap.put(i, new SimpleDateFormat(excelHelper.getFormat()).format(f.get(l)));
} else if (BigDecimal.class.isAssignableFrom(excelHelper.getClazz())) {
int scale = (excelHelper.getScale() != null && excelHelper.getScale().intValue() > 0)
? excelHelper.getScale().intValue()
: 2;
contentMap.put(i, String.valueOf(BigDecimal.valueOf(Double.valueOf(String.valueOf(f.get(l))))
.setScale(scale, BigDecimal.ROUND_HALF_UP)));
} else if (Boolean.class.isAssignableFrom(excelHelper.getClazz())) {
contentMap.put(i, Boolean.valueOf(f.get(l).toString()) ? excelHelper.getTrueName()
: excelHelper.getFalseName());
} else if (Integer.class.isAssignableFrom(excelHelper.getClazz())) {
contentMap.put(i, String.valueOf(Integer.valueOf(f.get(l).toString())));
}
}
data.add(contentMap);
}


return data;
}
}

通過以上程式碼,我們只要把Excel表對應的實體類的註解及規則定義好,就可以實現內容格式驗證,匯入匯出功能了

現在開始測試吧


public class Test {


public static void main(String[] args) throws Exception {
String path = Test.class.getResource("import.xls").toURI().getPath();
File file = new File(path);


System.out.println(checkExcelTitleAndSort(file));


System.out.println(checkExcelContent(file));


excelToEntity(file);


//exportExcelFile(loadData(), "d:" + File.separator + "export.xls");
}


/**
* 驗證Excel檔案首部的列名及排列順序是否跟定義的OrderForm類一致

* @param file
* @return
*/
public static boolean checkExcelTitleAndSort(File file) {
InputStream stream = null;
Workbook rwb = null;
Boolean check = false;
try {
stream = new FileInputStream(file);
// 獲取Excel檔案物件
rwb = Workbook.getWorkbook(stream);
// 獲取檔案的指定工作表 預設的第一個
Sheet sheet = rwb.getSheet(0);
Map<Integer, String> titleAndSortMap = ExcelUtil.getInstance().getExcelFieldName(OrderForm.class);
check = ExcelUtil.getInstance().equalsArrays(sheet, titleAndSortMap);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rwb != null) {
rwb.close();
}
}
return check;
}


/**
* 驗證Excel檔案的內容格式是否正確

* @param file
* @return
*/
public static boolean checkExcelContent(File file) {
InputStream stream = null;
Workbook rwb = null;
Boolean check = false;
try {
stream = new FileInputStream(file);
// 獲取Excel檔案物件
rwb = Workbook.getWorkbook(stream);
// 獲取檔案的指定工作表 預設的第一個
Sheet sheet = rwb.getSheet(0);
// 如有驗證失敗,該方法會返回錯欄位的欄位名稱
String result = ExcelUtil.getInstance().checkExcelContent(sheet, OrderForm.class);


// 如果沒有返回錯誤欄位,表示驗證通過
if (!Validator.isEffective(result)) {
check = true;
} else {
System.out.println(result);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rwb != null) {
rwb.close();
}
}
return check;
}


/**
* 將Excel檔案的內容轉換成實體類

* @param file
*/
public static void excelToEntity(File file) {
InputStream stream = null;
Workbook rwb = null;
try {
stream = new FileInputStream(file);
// 獲取Excel檔案物件
rwb = Workbook.getWorkbook(stream);
// 獲取檔案的指定工作表 預設的第一個
Sheet sheet = rwb.getSheet(0);
List<OrderForm> orderForms = ExcelUtil.getInstance().importExcelToEntity(sheet, OrderForm.class);
System.out.println(orderForms);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (rwb != null) {
rwb.close();
}
}
}


/**
* 將實體類的內容輸出到Excel檔案裡

* @param list
* @param filePath
*/
public static <T> void exportExcelFile(List<T> list, String filePath) {
OutputStream os = null;
try {
os = new FileOutputStream(new File(filePath));
List<Map<Integer, String>> dataList = ExcelUtil.getInstance().exportExcel(list);
WritableWorkbook wbook = Workbook.createWorkbook(os); // 建立excel檔案
String tmptitle = "訂單資訊";
WritableSheet wsheet = wbook.createSheet(tmptitle, 0); // sheet名稱
// 設定excel標題
WritableFont wfont = new WritableFont(WritableFont.ARIAL, 16, WritableFont.BOLD, false,
UnderlineStyle.NO_UNDERLINE, Colour.BLACK);
WritableCellFormat wcfFC = new WritableCellFormat(wfont);
wcfFC.setBackground(Colour.AQUA);
wsheet.addCell(new Label(1, 0, tmptitle, wcfFC));
wfont = new jxl.write.WritableFont(WritableFont.ARIAL, 14, WritableFont.BOLD, false,
UnderlineStyle.NO_UNDERLINE, Colour.BLACK);
wcfFC = new WritableCellFormat(wfont);
// 開始生成主體內容
// new Label(橫座標_X,縱座標_Y,列印的名稱)
for (int i = 0; i < dataList.size(); i++) {
for (int j = 0; j < dataList.get(i).size(); j++) {
wsheet.addCell(new Label(j, i, dataList.get(i).get(j)));
}
}


// 寫入檔案
wbook.write();
// 主體內容生成結束
wbook.close();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}


public static List<OrderForm> loadData() {
List<OrderForm> orderForms = new ArrayList<>();
OrderForm o = new OrderForm();
o.setCreateDate(new Date());
o.setCustomerName("張三");
o.setOrderNo("1122334455");
o.setOrderAmt(BigDecimal.valueOf(1000d));
o.setDiscountAmt(BigDecimal.valueOf(100d));
o.setIsMember(true);
orderForms.add(o);
OrderForm o2 = new OrderForm();
o2.setCreateDate(new Date());
o2.setCustomerName("李四");
o2.setOrderNo("1234567890");
o2.setOrderAmt(BigDecimal.valueOf(500d));
o2.setDiscountAmt(BigDecimal.valueOf(20d));
o2.setIsMember(false);
orderForms.add(o2);
return orderForms;
}
}


這張表的內容是沒有問題的,測試通過。並轉換成對應的OrderForm實體類


這個excel的格式有問題的,所以驗證失敗,並提示有錯誤的列名。



最後測試匯出excel檔案了



所有測試全部通過,其實只要把excel對應的實體類及註解定義好,使用jxl-api也可以很方便的使用excel表的,並不一定要用poi。

程式碼下載地址:http://download.csdn.net/download/u011634836/10127309

github下載地址:https://github.com/gzdavidxiang/MyExcelTools