1. 程式人生 > >專案中一個查詢列表突然無法查詢到資料-Mybatis的懶載入問題

專案中一個查詢列表突然無法查詢到資料-Mybatis的懶載入問題

最近在做一個專案,前期執行一直良好,某次測試突然發現一個查詢列表展示的小模組,突然就沒有資料了,然後瀏覽器F12除錯就會發現一堆的錯誤提示:

Failed to load resource: http://127.0.0.1:8090/XXX/static/lib/js/jquery-1.8.0.min.js 
the server responded with a status of 500 ()

莫名其妙,怎麼就500錯誤了,後臺資料庫明明良好,然後對專案進行除錯執行,也沒發現異常,後臺的controller正常執行完畢,並且list資料正確返回了,就是到前臺頁面就出現問題。

解決了一天也沒頭緒,一直以為是前端出問題了,第二天,程式碼跟蹤到了mapper檔案裡面了,

咦?這有意思了,前人在mapper裡面這樣定義的:

<resultMap id="main" type="com.dtjx.model.systemset.Specialfield">
        <id column="id" property="id" />
        <result column="id" property="id" />
        <result column="itemvalue" property="itemvalue" />
        ...不重要欄位省略...
        <association
property="itemname" column="nameid" javaType="String" select="com.gph.saviorframework.dao.config.SubItemMapper.changeIdToValue"/>
<association property="routesname" column="routes" javaType="String" select="com.dtjx.dao.route.RouteMapper.changeIdToValue"/> <association property
="citysname" column="citys" javaType="String" select="com.dtjx.dao.route.RouteMapper.changeIdToValue"/>
<collection property="showArr" ofType="com.dtjx.model.systemset.SpecialFieldText" > <result column="value" property="value"/> <result column="text" property="text"/> </collection> </resultMap>

發現上面的關聯查詢,用了很多 association 和 collection

同時對Controller下斷點跟蹤會發現,後臺查詢資料庫返回的資料列表:

斷點除錯list的內容
進入物件內部檢視

發現這個封裝的物件並不是我們自己定義的java bean ,很顯然這個是被代理過的,所以現在很容易懷疑這個問題並不是前臺的,而是後臺的bug,研發的小姐姐估計也沒想到吧,mybatis的resultMap並不是隨處可用的,一個簡單的列表只需要展現需要的欄位即可,不要載入居多的其他資訊。

解決方法肯定有了,另寫一個查詢list列表的resultMap就行了,把association 和 collection的關聯去掉就能正常在前臺也沒展示了!

到這裡並沒有完

出現上面問題,我們分析更深層的原因,老鐵們可以參考這篇部落格:Mybatis的巢狀查詢和延遲載入分析 ,真正的原因就是Mybatis的延遲載入,出現上面情況就是用了其中的javassist方法來實現延遲載入,導致前臺無法解析json

實際上這個問題,廣大網友早就友解決方法了,參考:mybitis懶載入Could not write JSON:No serializer…
並且有具體的報錯資訊,如下(奇怪,為啥我這裡除錯就沒有異常資訊呢?):

json:Could not write JSON: No serializer found for class org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory$EnhancedResultObjectProxyImpl and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class org.apache.ibatis.executor.loader.javassist.JavassistProxyFactory$EnhancedResultObjectProxyImpl and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.xfishtour.entity.result2.JsonResult[\"data\"]->java.util.ArrayList[0]->com.xxx.xxxx_$$_jvstc2b_0[\"handler\"])

看到這異常提示,解決方法就更簡單了,上面文章的老鐵就給出了3中方法:

  • 關閉該查詢的懶載入 fetchType=”eager”
<collection ...  fetchType="eager">
</collection>
  • 返回的類加上註解
@JsonIgnoreProperties(value = { "handler" })

直接將handler忽略掉,這個屬於jackson中的內容,mybatis中使用jackson處理json資料

  • 配置json轉換器屬性SerializationFeature.FAIL_ON_EMPTY_BEANS為false。

這還沒有完

革命尚未成功同志尚需努力啊!

後期在看這個Controller的查詢,發現一個很搞笑的問題,傳說中的“胡隆人”/或者惡意預留bug

這位程式小姐姐是這樣寫滴:

@RequestMapping(value = "/getlist")
@ResponseBody
public ModelMap getlist(
        @RequestParam(value = Constants.DEFAULT_CURRPAGE_MODEL_KEY, required = false, defaultValue = Constants.DEFAULT_PAGE_START) Integer start,
        @RequestParam(value = Constants.DEFAULT_PAGE_SIZE_MODEL_KEY, required = false, defaultValue = Constants.DEFAULT_PAGE_SIZE) Integer limit,
        Specialfield model) {
    ModelMap modelMap = new ModelMap();
    try {
        User userinfo = user();
        if (userinfo != null) {
            if (isNotEmpty(userinfo.getRoutes())) {
                List<String> trains = getRouteIds(userinfo);
                if (trains.size() > 0) {
                    model.setRouteIdAuthority(trains);
                }
            }
        // 分頁的關鍵程式碼開始
        List<Specialfield> list = specialfieldService.get(model);
        int startmax = list.size() == 0 ? 0 : list.size() - 1;
        int fromIndex = Math.min((start - 1) * limit, startmax);
        int toIndex = Math.min(start * limit, list.size());
        modelMap.addAttribute(Constants.DEFAULT_RECORD_MODEL_KEY, list.subList(fromIndex, toIndex));
        modelMap.addAttribute(Constants.DEFAULT_COUNT_MODEL_KEY, list.size());
        // 分頁結束
    } else {
            modelMap.addAttribute(Constants.DEFAULT_SUCCESS_KEY, Boolean.FALSE);
        }
    } catch (Exception e) {
        modelMap.addAttribute(Constants.DEFAULT_SUCCESS_KEY, Boolean.FALSE);
        modelMap.addAttribute(Constants.DEFAULT_ERROR_KEY, e.getMessage());
    }
    return modelMap;
}

看一下主要的分頁程式碼

List<Specialfield> list = specialfieldService.get(model);
int startmax = list.size() == 0 ? 0 : list.size() - 1;
int fromIndex = Math.min((start - 1) * limit, startmax);
int toIndex = Math.min(start * limit, list.size());
modelMap.addAttribute(Constants.DEFAULT_RECORD_MODEL_KEY, list.subList(fromIndex, toIndex));
modelMap.addAttribute(Constants.DEFAULT_COUNT_MODEL_KEY, list.size());
  • 第一行,查詢資料
  • 第二行,計算資料總數
  • 第三行,從第一個開始
  • 第四和,從哪裡結束
  • 第五行,分頁取資料,並放給前臺
  • 第六行,給前臺總數

這個分頁大概是可以吧?不過並不好,並且是非常不好,查詢個三百五百條的沒有問題,當時設想一下,這個表裡面有十萬條資料咋辦?

每次都獲取全部,那還用分頁幹啥啊?

我接收後,看到上面的程式碼,隨手就改了,如下:

PageHelper.startPage(start, limit);
List<Map<String, Object>> list = specialfieldService.getlist(model);
modelMap.addAttribute(Constants.DEFAULT_RECORD_MODEL_KEY, list);
modelMap.addAttribute(Constants.DEFAULT_COUNT_MODEL_KEY, ((Page<Map<String, Object>>) list).getTotal());
modelMap.addAttribute(Constants.DEFAULT_SUCCESS_KEY, Boolean.TRUE);

很easy,是不是,用的PageHelper外掛去做的分頁操作,也跟專案中框架用的統一的分頁一致,為啥還要用list的擷取分頁呢?

當時沒考慮,後來才發現小姐姐其實也是無奈呢~~~

PageHelper外掛跟Mybatis中的collection一對多關聯查詢分頁總是出現問題,莫名奇妙,資料分頁總是不對!

專案中用到了pageHelper,但是在mybatis的resultmap中使用了collection去封裝一對多的對映關係。

分頁查詢後始終記錄數和實際資料對不上,原因出在pageHelper和collection有衝突。

原因: pagehelper是基於sql查詢結果的資料條數進行攔截的,但是collection會把相同結果對映為List,

所以執行順序是先攔截條數,後封裝結果,造成了資料分頁後查詢數量變少。

看看原先mapper的xml寫法,如下:

<resultMap id="main" type="com.dtjx.model.systemset.Specialfield">
    <id column="id" property="id" />
    <result column="id" property="id" />
    ....
    <collection property="showArr" ofType="com.dtjx.model.systemset.SpecialFieldText" fetchType="lazy">
     <result column="value" property="value"/>
     <result column="text" property="text"/>
    </collection>
</resultMap>

<select id="getlist" parameterType="com.dtjx.model.systemset.Specialfield" resultMap="main">
    SELECT
        a.id,
        ...,
        d.text AS text,
        d.value AS value
    FROM
        dtjx_specialfield a
    LEFT JOIN dtjx_transmissioncode b ON a.codeid = b.id
    LEFT JOIN sf_sub_item c ON a.nameid = c.SUB_ITEM_ID
    LEFT JOIN dtjx_special_field_text d ON a.id = d.specialFieldId
    WHERE
        1 = 1
</select>

上面的查詢會直接把有相同id下的text和value封裝成list,假設分頁是每頁10條,那麼就會如果list超過10條,也只能被pagehelper取出10條記錄,並且返回給頁面的也只有一條記錄;

那麼怎麼解決呢?

這種情況下,不應該使用上面的寫法,應該改成使用關聯查詢,如下:

<resultMap id="main" type="com.dtjx.model.systemset.Specialfield">
    <id column="id" property="id" />
    <result column="id" property="id" />
    ....
<collection property="showArr" ofType="com.dtjx.model.systemset.SpecialFieldText" column="id"
            fetchType="lazy" select="com.dtjx.dao.systemset.SpecialfieldMapper.showArrById">
        </collection>
</resultMap>
<!--增加關聯查詢 -->
 <select id="showArrById" parameterType="string" resultType="com.dtjx.model.systemset.SpecialFieldText">
    select value, text from dtjx_special_field_text where specialFieldId = #{id}
</select>

<select id="getlist" parameterType="com.dtjx.model.systemset.Specialfield" resultMap="main">
    SELECT
        a.id,
        ...,<!-- 去掉相關的 text和value,不要left join 有關text和value的表
        d.text AS text,
        d.value AS value -->
    FROM
        dtjx_specialfield a
    LEFT JOIN dtjx_transmissioncode b ON a.codeid = b.id
    LEFT JOIN sf_sub_item c ON a.nameid = c.SUB_ITEM_ID
    WHERE
        1 = 1
</select>

一定要讓主查詢去做分頁操作,其他的作為關聯查詢,這就OK啦!