實習總結四:週報,解決匯出功能效率過慢問題
哎,很慚愧,一個多月沒寫部落格了。一直在等這個專案上線後更新部落格,今天終於上線了(當然我希望需求不會再更改)。而且最近學習salseforce,學習路漫漫!加油!!
週報
這個週報的專案其實是給公司內部使用的。使用Mysql和Mongo兩個資料庫。
- Mysql是用來儲存人員資訊。
- Mongo是用儲存週報的資訊、以及一些相關的人員配置,週報配置。
增刪改查就不說了,沒意思。說說下面幾個
- 設定管理員:使用者不必每次新建週報都要選擇分享人以及領導人。
- 匯出週報:說起這個我就來氣。就這個簡單的東西非要搞得很奇怪。不匯出週報資訊,要匯出人員資訊= =,一會再具體說吧。
- 設定管理員:管理員有許可權進行查詢匯出。
controller
/** * 週報匯出(以及匯出前分頁查詢) * * @param queryBean * 封裝分頁查詢 * @param operation * 判斷是查詢還是匯出.值為'export'時是匯出;為空或者任意字串時,是分頁查詢。 * @return */ @RequestMapping(value = "/apply-export", method = RequestMethod.GET) @ApiOperation(value = "週報匯出及分頁查詢", notes = "分頁查詢時'operation'可以為空,或為任意字串;當需要匯出時,'operation'必須為'export'", responseReference = "Page", produces = "application/json") public Rest getReports(QueryBean queryBean , @RequestParam String operation) { QContext context = getContext(); // 判斷是否有匯出許可權 if(!workReportService.isAdmin(context)) { return Rest.info(Sys.StateCode.STATUS_CODE_FORBIDDEN, "對不起,沒有匯出許可權"); } WebParamInfo webParamInfo = getWebParamInfo(queryBean); if(!StrUtils.isEmpty(operation) && "export".equals(operation)) { HttpServletResponse response = getResponse(); HttpServletRequest request = getRequest(); workReportService.exportReport(context, webParamInfo, response, request); return null; }else { Page<WorkReport> list = workReportService.findExportReportBypage(context, webParamInfo); return Rest.page(Sys.StateCode.STATUS_CODE_SUCCESS, list); } }
其實整個專案沒有什麼難度,之所以做的慢,是因為資料錯誤(這個我不是負責,只是簡單的看了看),以及匯出速度太慢。一直在優化,影響最大的地方就是查詢匯出,因為要匯出的是人員基本資訊和職位資訊,這是兩個表,我需要去查當前週報建立人資訊。在客戶端查的時候,才幾十毫秒,很快。但是一旦通過程式碼進行實現,就變成分鐘級別,很恐怖。而且人真不多,9000而已。
我先說說幾次優化吧。
最下面的程式碼是最終的解決方案。用了最笨的辦法但是也是最有效的,時間換空間。將所有人員資訊在建立週報的時候就存放在一個javabean中,匯出的時候直接取週報就行。這樣確實快了很多。用指令碼測的時候查詢週報的時候略慢,但是完全能接受了。
第一次:也就是剛寫出來的時候。想的很簡單,因為是多個週報麼,每個週報都需要設定資料,如果每次都查詢的話,那1000個週報,1000個人建立人,要我查1000次?太恐怖了,還不如一次性全部查出來。但是這個時候其中一個問題就出來了,幾條記錄怎麼辦?先試試查所有要多久吧,OK。Person person = personService.findall();這個查詢速度真的太慢了。肯定是不行的。然後我試了試新增查詢條件,查詢幾個人,還是很快的,稍微多一點就不行了。所以這個方法pass掉。
第二次:其實也是第一次的遺留問題,就算我匯出1w條記,錄,但是其實就是100個人建立的。就是一個建立人存在多條週報。OK,那我就設定一個集合,存放已經查詢過得人的資訊。每次查詢之前先去找是否存在。
// creaters集合,存放建立人
Map<String, Person> creaters = new HashMap<String, Person>();
Person person =null;
String id = workReport.getCreateBy().getId();
// 判斷當前週報建立人是否存在。如果不存在,再去查詢,同時放進map集合中
if(creaters.get(id) == null) {
person = personService.findByItcode(context, workReport.getCreateBy().getItcode());
creaters.put(id, person);
}else {
person = creaters.get(id);
}
其實這麼寫,還是老問題,建立人多了,查詢就變慢了。哎。。。因為這個我還惡補了一下java程式碼優化。把整個匯出部分的程式碼都優化了一遍。效果甚微啊。程式碼很長,但是都是需要的欄位,邏輯部分其實很少。
程式碼既然優化到此結束,查詢的慢,那就是資料的問題了。丟擲該person表中一大堆無用欄位不提。好多的約束條件我也是第一次看到。這麼多的約束條件,對查詢速度肯定會有影響,而且當表大了之後,影響會更大。約束,更多是為了保證資料的正確性,一定程度上保證了安全性。而正確性、安全性和效能是天平的兩個方面,就像魚和熊掌,不可兼得。
Impl-最終的解決方案
/**
*程式碼中的exportPersonInformation就是放在週報中的欄位,用來存放人員資訊。
*/
@Override
public void exportReport(QContext context, WebParamInfo webParamInfo, HttpServletResponse response,
HttpServletRequest request) {
Criteria criteria = Criteria.where("org").is(context.getOrg()).and("status").is(SUBMIT);
Map<String, Object> param = webParamInfo.getParam();
Object filterObject = param.get("filterUsers");
List<String> userIds = getUserIds(filterObject);
if (userIds != null && userIds.size() > 0) {
criteria.and("createBy._id").in(userIds);
}
// 根據日期範圍進行查詢
Criteria dateCrita = getDateCrita(param);
criteria.andOperator(dateCrita);
Query query = new Query();
query.addCriteria(criteria);
querySetSort(query, webParamInfo);
List<WorkReport> workReports = findAll(context, query);
if (workReports != null && workReports.size() > 0) {
try {
Map<String, String> heads = new TreeMap<String, String>();
heads.put("A", "週報名稱");
.
.
.
heads.put("Y", exportPersonInformation.getName());
List<Map<String, String>> datas = new ArrayList<Map<String, String>>();
datas.add(heads);// 新增表頭
for (WorkReport workReport : workReports) {
datas.add(setData(workReport));// 新增資料
}
String date = DateUtils.convertDateToString(new Date());
String fileName = "週報匯出" + date + ".xls";
response.setHeader("Content-disposition",
"attachment; filename=" + new String(fileName.getBytes("GB2312"), "ISO-8859-1"));
OutputStream outStream = response.getOutputStream();
ExcelExport export = new ExcelExport();
export.export(outStream, datas);// 匯出excel
} catch (Exception e) {
e.printStackTrace();
}
}
}
private Map<String, String> setData(WorkReport workReport) {
Map<String, String> map = new HashMap<String, String>();
ExportPersonInformation exportPersonInformation = workReport.getExportPersonInformation();
// 設定資料
map.put("A", DateUtils.getDateStr(workReport.getStartDate()) + "~" + DateUtils.getDateStr(workReport.getEndDate()) + "的週報");// 週報名稱
.
.
.
map.put("Y", *****);
return map;
}