專案中一個查詢列表突然無法查詢到資料-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下斷點跟蹤會發現,後臺查詢資料庫返回的資料列表:
發現這個封裝的物件並不是我們自己定義的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啦!