1.開發背景
在web項目中,經常會需要查詢數據導出excel,以前比較常見的就是用poi。使用poi的時候也有兩種方式,一種就是直接將集合一次性導出為excel,還有一種是分批次追加的方式適合數據量較大的情況。poi支持xls和xlsx,使用2003版本的只支持6萬多行以下的數據量,使用2007版本的支持百萬行。但是呢,當數據量大了之后這種方式卻非常耗內存和時間。
接觸了etl之后就想着用kettle來做導數據,經過測試是完全可行的。幾十萬行,一百萬行都能快速導出來,代碼也非常簡單。
2.kettle相關maven依賴如下

1 <dependency> 2 <groupId>org.apache.commons</groupId> 3 <artifactId>commons-vfs2</artifactId> 4 <version>2.0</version> 5 </dependency> 6 <dependency> 7 <groupId>org.scannotation</groupId> 8 <artifactId>scannotation</artifactId> 9 <version>1.0.3</version> 10 </dependency> 11 <dependency> 12 <groupId>dom4j</groupId> 13 <artifactId>dom4j</artifactId> 14 <version>1.6.1</version> 15 </dependency> 16 <dependency> 17 <groupId>pentaho-kettle</groupId> 18 <artifactId>kettle-vfs</artifactId> 19 <version>5.2.0.0</version> 20 <classifier>pentaho</classifier> 21 </dependency> 22 <dependency> 23 <groupId>pentaho-kettle</groupId> 24 <artifactId>kettle-engine</artifactId> 25 <version>5.2.0.0</version> 26 </dependency> 27 <dependency> 28 <groupId>pentaho-kettle</groupId> 29 <artifactId>kettle-core</artifactId> 30 <version>5.2.0.0</version> 31 </dependency>
倉庫如果沒有kettle的jar包,可以先現在下來再上傳到maven倉庫
3.ktr文件:如以下附件下載鏈接
由於博客園不支持ktr路徑的文件上傳,所以我將它保存為xml文件,使用時將xml后綴去掉用ktr后綴就可以 了,該轉換就是查詢,導出為excel兩個組件,如圖所示:

這里用到一個輸入和excel輸出,里面配置的參數:
查詢語句: ${exec_select_sql}、
文件名稱:${filepath}、
sheet名稱:${sheetname}
4.調用ktr

1 /** 2 * @功能描述: java調用Kettle導出的KTR,方法調用成功后,通過filepath參數獲取文件<br><font color="red">該程序已經指定數據源</font> 3 * @創建作者: *** 4 * @創建日期: 2016年11月1日 下午7:50:57 5 * @param exec_select_sql:可執行的SELECT語句(案例:SELECT username '名稱',userName '員工名稱',ID 'ID' FROM `User`;) 6 * @param filepath:保存的文件名稱,不含后綴,后綴統一xlsx(案例:C:\\test) 7 * @param sheetname:文件中的sheet名稱(默認:下載) 8 * @return 9 */ 10 public static boolean exportXlsx(String exec_select_sql, String filepath, String sheetname) { 11 if(StringUtils.isEmpty(exec_select_sql)||StringUtils.isEmpty(filepath)) 12 return false; 13 Trans trans = null; 14 if(StringUtils.isEmpty(sheetname)) sheetname = "下載"; 15 String uuid = UUID.randomUUID().toString(); 16 logger_info.info("KettleUtil@exportXlsx:"+uuid+" {exec_select_sql:"+exec_select_sql+",filepath:"+filepath+",sheetname:"+sheetname+"}"); 17 try { 18 String root_path = getPathMethod(); 19 // 初始化 20 String fName = root_path+"export_xlsx.ktr"; 21 // 轉換元對象 22 KettleEnvironment.init();// 初始化 23 EnvUtil.environmentInit(); 24 TransMeta transMeta = new TransMeta(fName); 25 // 轉換 26 trans = new Trans(transMeta); 27 // 執行轉換 28 trans.setVariable("exec_select_sql", exec_select_sql); 29 trans.setVariable("filepath", filepath); 30 trans.setVariable("sheetname", sheetname); 31 trans.execute(null); 32 // 等待轉換執行結束 33 trans.waitUntilFinished(); 34 // 拋出異常 35 if (trans.getErrors() > 0) { 36 logger_info.info("KettleUtil@exportXlsx:"+uuid+" 執行失敗"); 37 }else{ 38 logger_info.info("KettleUtil@exportXlsx:"+uuid+" 執行成功"); 39 } 40 return true; 41 } catch (Exception e) { 42 logger_error.error("KettleUtil@exportXlsx:"+uuid, e); 43 return false; 44 } 45 } 46 47 /** 48 * @功能描述: 獲取編譯目錄 49 * @創建作者: *** 50 * @創建日期: 2016年11月1日 下午7:59:13 51 * @return 52 */ 53 private static String getPathMethod(){ 54 URL url= KettleUtil.class.getClassLoader().getResource(""); 55 String p = url.getPath(); 56 try { 57 p=URLDecoder.decode(p, "UTF-8"); 58 } catch (UnsupportedEncodingException e) { 59 logger_error.error("KettleUtil@getPathMethod:", e); 60 } 61 return p; 62 }
5.測試導出方法
web項目中的測試
@RequestMapping
(
"/kettle"
)
public
Object kettle(
int
rows, String sql) {
String sqlLimit = sql +
"LIMIT "
+rows;
String fullName = "/home/admin/DataPlatform/temp"+
"/kettle"
+uuid;
this
.kettleExportExcel(sqlLimit, fullName,
"kettle"
);
return
null
;
}
也可以用main函數或junit測試
6.打印執行信息,也可以直接在程序里面加

@Component @Aspect public class ControllerAspect { private static Logger logger_info = Logger.getLogger("api-info"); private static Logger logger_error = Logger.getLogger("api-error"); /** * 切面 */ private final String POINT_CUT = "execution(* com.demo.controller.*.*(..))"; @Pointcut(POINT_CUT) private void pointcut() { } @AfterThrowing(value = POINT_CUT, throwing = "e") public void afterThrowing(Throwable e) { logger_error.error("afterThrowing: " + e.getMessage(), e); } /** * @功能描述: 打印Controller方法的執行時間 * @創建日期: 2016年11月2日 上午11:44:11 * @param proceedingJoinPoint * @return * @throws Throwable */ @Around(value = POINT_CUT) public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { String className = proceedingJoinPoint.getTarget().getClass().getName(); String methodName = proceedingJoinPoint.getSignature().getName(); Long begin = System.currentTimeMillis(); Long beginMemory = Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory(); StringBuilder log = new StringBuilder(className+"@"+methodName); Object result = null; try { result = proceedingJoinPoint.proceed(); } catch (Exception e) { logger_error.error(log + e.getMessage(), e); } Long end = System.currentTimeMillis(); Long endMemory = Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory(); log.append(" 執行時間: ").append(end - begin).append("ms"); log.append(" 消耗內存: ").append(endMemory - beginMemory).append("Byte"); logger_info.info(log); return result; } }
7.執行結果
* 導出10w行記錄
執行時間: 1133ms
執行時間: 1082ms
執行時間: 1096ms
* 導出100w行記錄
執行時間: 39784ms
執行時間: 8566ms
執行時間: 8622ms
* Excel 2007行數極限 1048575 執行時間: 9686ms
第一次導數據要加載kettle組件運行稍慢,后面幾次再導數據速度就飛快了,更多結果有興趣的可以去試試。
僅供參考,不足之處還請見諒,歡迎指正!轉載請標明出處。如有疑問,歡迎評論或者聯系我郵箱1034570286@qq.com