Quartz + Tablesaw 報表統計
阿新 • • 發佈:2018-01-23
beans cor -a targe quertz entryset 列式存儲 文件導入 else
場景
在12 月份做的報表功能中,直接從 ES 查詢一個月的數據。當數據量特別大時,查詢速度會非常緩慢甚至查詢失敗。解決方案是使用定時任務,在每天淩晨指定時間自動查詢前一天的數據,然後寫入 CSV 文件中,每天追加。生成報表文件時,就不用再查詢 ES,而是讀取 CSV 文件,統計一個月每天數據的總和。
一、定時任務
定時任務使用的是 Quartz 框架。
Quartz 是什麽
Quartz 是一個開源的作業調度框架,由 java 編寫,在.NET 平臺為 Quartz.Net,通過 Quart 可以快速完成任務調度的工作。
Quartz 使用場景
如定時發送郵件、定時統計數據生成報表等等
在項目中使用 Quartz
添加依賴
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>
spring-quartz.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <!-- 要調用的工作類 --> <bean id="quartzJob" class="com.devywb.quartzJob"></bean> <!-- 定義調用的對象和方法 --> <bean id="jobTask" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <!-- 調用的類 --> <property name="targetObject"> <ref bean="quartzJob"></ref> </property> <!-- 調用的方法 --> <property name="targetMethod"> <value>work</value> </property> </bean> <!-- 定義觸發的時間 --> <bean id="doTime" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"> <property name="jobDetail"> <ref bean="jobTask"/> </property> <!-- cron表達式 --> <property name="cronExpression"> <!-- 每隔20秒鐘執行一次 --> <!-- <value>*/20 * * * * ?</value> --> <!-- 每天淩晨五點執行 --> <value>0 0 5 * * ?</value> </property> </bean> <!-- 總管理類 --> <bean id="startQuertz" lazy-init="false" autowire="no" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="doTime"/> </list> </property> </bean> </beans>
二、Tablesaw
Tablesaw是一套內存內數據表,其中包含多種數據工具與面向列的存儲格式。其設計思路認為沒人會面向小型任務執行分布式分析,而大家可以在單一服務器上對200萬行級別的表進行交互。
大家能夠利用Tablesaw執行各種規則,從而檢查顯示布局、數據優先級或者針對數據顯示及交互向特定用戶提供擴展控制範圍。在它的幫助下,我們可以利用RDBMS與CSV文件導入數據,添加及刪除列,執行映射與規約操作或者將表保存在經過壓縮的列式存儲格式當中。
添加依賴
<!-- https://mvnrepository.com/artifact/tech.tablesaw/tablesaw-core--> <dependency> <groupId>tech.tablesaw</groupId> <artifactId>tablesaw-core</artifactId> <version>0.11.2</version> <exclusions> <exclusion> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </exclusion> <exclusion> <groupId>org.apache.commons</groupId> <artifactId>commons-math3</artifactId> </exclusion> </exclusions> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.0</version> </dependency> <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-math3 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-math3</artifactId> <version>3.0</version> </dependency>
在項目中添加 Tablesaw 的依賴後,項目無法啟動,原因是 commons-lang3 和 commons-math3 兩個依賴的版本太高了。後來采用的解決方法是排除依賴再另外引入低版本依賴。
常用 API
// 讀取 csv 文件
Table table = Table.read().csv(String filePath);
// 獲取所有列名
List<String> columnNames = table.columnNames();
// sum
table.sum("列名").get();
// 排序
table.sortDescendingOn("列名"); // 降序
table.sortAscendingOn("列名"); // 升序
// 分組
table.groupBy(columns);
// 前多少條
table.first(nRows)
// 生成 saw 文件
Table table = Table.read().csv(contents, tableName);
table.save(folder);
// 讀取 saw 文件
Table table = Table.readTable(tableNameAndPath)
踩過的坑
寫入數據至 CSV 文件時,當數據中某個字段包含分隔符或其他特殊符號時,程序會報錯。下面列出兩種解決方案。
第一種,使用第三方庫,例如 openCSV,它底層對特殊符號做了處理;
第二種,手動處理字段中的特殊符號;
/**
* list 轉 CSV 字符串
* @param header
* @param data
* @return
*/
public static String list2CsvStr(Map<String, String> header, List<Map<String, Object>> data) {
String content = "";
if (header != null && header.size() > 0) {
if (data != null && data.size() > 0) {
StringBuilder sb = new StringBuilder();
for (Map<String, Object> map : data) {
Set<Entry<String, String>> entrySet = header.entrySet();
int i = 0;
for (Entry<String, String> entry : entrySet) {
String key = entry.getKey();
if(i > 0) sb.append(SEPARATOR); // 不是第一列時,添加分隔符
if(map.containsKey(key)) {
Object value = map.get(key);
String valueStr = value != null ? value.toString() : "";
if(valueStr.contains(SEPARATOR)) { // 如果數據包含分割符
if(valueStr.contains("\"")) { // 如果字段中包含雙引號,替換成兩個
valueStr.replace("\"", "\"\"");
}
// 如果字段包含分割符,則用雙引號括起來
valueStr = "\"" + valueStr +"\"";
}
sb.append(valueStr);
} else {
sb.append("");
}
i ++ ;
}
sb.append("\n");
}
content = sb.toString();
}
}
return content;
}
/**
* map 轉 CSV 字符串
* @param header
* @param data
* @return
*/
public static String map2CsvStr(Map<String, String> header, Map<String, Object> data) {
String content = "";
if (header != null && header.size() > 0) {
if (data != null && data.size() > 0) {
StringBuilder sb = new StringBuilder();
int i = 0;
Set<Entry<String, String>> entrySet = header.entrySet();
for (Entry<String, String> entry : entrySet) {
String key = entry.getKey();
if(i > 0) sb.append(SEPARATOR);
if(data.containsKey(key)) {
Object value = data.get(key);
String valueStr = value != null ? value.toString() : "";
if(valueStr.contains(SEPARATOR)) { // 如果數據包含分割符
if(valueStr.contains("\"")) { // 如果字段中包含雙引號,替換成兩個
valueStr.replace("\"", "\"\"");
}
// 如果字段包含分割符,則用雙引號括起來
valueStr = "\"" + valueStr +"\"";
}
sb.append(valueStr);
} else {
sb.append("");
}
i ++;
}
sb.append("\n");
content = sb.toString();
}
}
return content;
}
Quartz + Tablesaw 報表統計