1. 程式人生 > >實習總結四:週報,解決匯出功能效率過慢問題

實習總結四:週報,解決匯出功能效率過慢問題

哎,很慚愧,一個多月沒寫部落格了。一直在等這個專案上線後更新部落格,今天終於上線了(當然我希望需求不會再更改)。而且最近學習salseforce,學習路漫漫!加油!!

週報

這個週報的專案其實是給公司內部使用的。使用Mysql和Mongo兩個資料庫。

  • Mysql是用來儲存人員資訊。
  • Mongo是用儲存週報的資訊、以及一些相關的人員配置,週報配置。

增刪改查就不說了,沒意思。說說下面幾個

  1. 設定管理員:使用者不必每次新建週報都要選擇分享人以及領導人。
  2. 匯出週報:說起這個我就來氣。就這個簡單的東西非要搞得很奇怪。不匯出週報資訊,要匯出人員資訊= =,一會再具體說吧。
  3. 設定管理員:管理員有許可權進行查詢匯出。

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;
}