問(wèn)題復(fù)現(xiàn):
我有兩張表抚笔,1張是學(xué)生表扶认,另一張是家屬表。學(xué)生與家長(zhǎng)是一對(duì)多關(guān)系∈獬龋現(xiàn)在有個(gè)需求辐宾,查出每個(gè)學(xué)生和他的家屬的姓名狱从,并分頁(yè)展示。關(guān)聯(lián)關(guān)系是叠纹,學(xué)生fa_id作為學(xué)生表的主鍵矫夯,家長(zhǎng)表內(nèi)通過(guò)學(xué)生fa_id與該學(xué)生關(guān)聯(lián)。
查詢方式是:
學(xué)生表fb left join 家長(zhǎng)表 fp on fb.fa_id = fp.fa_id
然后由mybatis根據(jù)嵌套了collection的resultMap來(lái)將數(shù)據(jù)進(jìn)行封裝吊洼。(問(wèn)題就出在嵌套的resultMap上)
傳輸對(duì)象如下:
public class UserBasicInfoTo {
private String faId; //學(xué)生id
private String fbName; // 學(xué)生名
private String fbPhone; //學(xué)生電話號(hào)碼
private List<String> agedPeople; //學(xué)生家屬名列表
public UserBasicInfoTo() {
}
//getters and setters
}
xml配置如下:
<resultMap id="UserInfoToMap" type="com.cloud.fmmp.base.bean.to.UserBasicInfoTo">
<result column="FB_Name" property="fbName"></result>
<result column="FA_ID" property="faId"></result>
<result column="FB_Phone" property="fbPhone"></result>
<collection property="agedPeople" ofType="java.lang.String">
<result column="pb_cnname"></result>
</collection>
</resultMap>
<select id="getUserInfoTos" resultMap="UserInfoToMap">
SELECT FB_Name, FB_Phone, fb.FA_ID, PB_CNName FROM ins_family_base fb
LEFT JOIN ins_family_people fp
ON fb.FA_ID = fp.FA_ID
WHERE fb.SYS_ID = #{sysId}
<if test="fbName != null">
and fb.FB_Name = #{fbName}
</if>
<if test="fbPhone != null">
and fb.FB_Phone = #{fbPhone}
</if>
</select>
對(duì)應(yīng)的mapper接口
@Repository
public interface InsFamilyBaseMapper extends BaseMapper<InsFamilyBase> {
List<UserBasicInfoTo> getUserInfoTos(@Param("sysId") String sysId, @Param("fbName") String fbName, @Param("fbPhone") String fbPhone);
}
service代碼片段
Integer pageNum = Integer.parseInt(map.get("pageNum"));
Integer pageSize = Integer.parseInt(map.get("pageSize"));
PageHelper.startPage(pageNum, pageSize);
// join兩張表,UserBasicInfoTo嵌套一個(gè)List
List<UserBasicInfoTo> tos = insFamilyBaseMapper.getUserInfoTos(sysId, fbName, fbPhone);
PageInfo<UserBasicInfoTo> pageInfo = new PageInfo<>(tos);
return pageInfo;
上述代碼在返回pageInfo時(shí)制肮,結(jié)果的總條數(shù)不對(duì)冒窍。查出來(lái)的對(duì)象個(gè)數(shù)為13,但是pageInfo卻顯示有19條記錄豺鼻,如下所示:
這里明顯是不對(duì)的,但哪里出問(wèn)題了呢儒飒?
在講resultSetHandler的時(shí)候谬莹,明確提到過(guò),它對(duì)有嵌套的resultMap的處理時(shí)桩了,做了一次ensureNoRowBounds的檢查附帽,即確保他自己沒(méi)有分頁(yè)參數(shù)。
也就是說(shuō)井誉,在mybatis的resultSetHandler處理原始帶有嵌套的resultMap時(shí)蕉扮,是沒(méi)有帶分頁(yè)參數(shù)的。那么這個(gè)時(shí)候pageHelper是怎么工作的呢颗圣?
我們實(shí)現(xiàn)一個(gè)interceptor攔截打印一下執(zhí)行的sql喳钟,就了然了。
1.把pageHelper注釋掉
sql查到了19條記錄在岂,此時(shí)并沒(méi)有封裝成UserBasicInfoTo奔则,即數(shù)據(jù)庫(kù)中有19條記錄是滿足查詢條件的。
然后resultSetHandler對(duì)結(jié)果集進(jìn)行處理封裝蔽午,對(duì)相同fa_id的結(jié)果集進(jìn)行合并處理(相同fa_id的結(jié)果集都封裝成1個(gè)infoTO對(duì)象易茬,對(duì)嵌套的resultMap處理并放入到該對(duì)象的List<String> agedPeople中。)所以處理完成之后就剩13個(gè)對(duì)象了.如圖
現(xiàn)在明白了祠丝,19是原始的記錄數(shù)疾呻,13是resultSetHandler封裝后的結(jié)果數(shù)。
那么為什么pageHelper能得到總的記錄數(shù)呢写半?
原因是他做了這樣一個(gè)操作:攔截了當(dāng)前執(zhí)行的sql語(yǔ)句岸蜗,并通過(guò)select count(0)語(yǔ)句得到了符合查詢條件的記錄數(shù),然后將其作為總頁(yè)數(shù),即19叠蝇。
然后璃岳,對(duì)sql語(yǔ)句進(jìn)行拼接年缎,加上分頁(yè)參數(shù)。
但是mybatis在處理statement的時(shí)候铃慷,將分頁(yè)參數(shù)提取出來(lái)了单芜,存放在了rowBounds中,向數(shù)據(jù)庫(kù)發(fā)送的query中并沒(méi)有l(wèi)imit a, b ,而是最后在resultSetHandler處理結(jié)果集的時(shí)候犁柜,跳過(guò)了分頁(yè)參數(shù)影響的那些行洲鸠。
[圖片上傳中...(image.png-f4c9f3-1616516429638-0)]
所以,pageHelper對(duì)嵌套的resultMap進(jìn)行分頁(yè)處理的時(shí)候馋缅,還有可能會(huì)導(dǎo)致嵌套的數(shù)據(jù)不完整扒腕,如Vo中應(yīng)當(dāng)包含一個(gè)List,該list中如果有數(shù)據(jù)被分頁(yè)參數(shù)跳過(guò)了,那么list內(nèi)的數(shù)據(jù)就不完整了萤悴。
最后舉幾個(gè)例子來(lái)說(shuō)明pageHelper在處理嵌套的resultMap時(shí)瘾腰,出現(xiàn)上面所說(shuō)的數(shù)據(jù)總數(shù)和數(shù)據(jù)不完整的問(wèn)題。
- pageNum =3, pageSize = 5, 轉(zhuǎn)換成sql則是limit 10, 5獲取第11-15條記錄覆履,假設(shè)這里面有3個(gè)不同的學(xué)生蹋盆,5個(gè)家長(zhǎng)。如果這三個(gè)學(xué)生還有其他滿足條件的記錄硝全,但這些記錄不在第11-15條之間栖雾,那么家長(zhǎng)信息就不完整了。另外柳沙,pageinfo的total是上面所說(shuō)的select count(0) from xxx where (columns subject to condition),也就是總的記錄數(shù)岩灭,但我現(xiàn)在要的是不同的學(xué)生數(shù),所以page中的count也不對(duì)了赂鲤。
-
舉個(gè)極端點(diǎn)的例子來(lái)說(shuō)噪径,pageNum = 2,時(shí),在pageSize = 1和pageSize = 20的兩種條件下数初,查詢出來(lái)的agedPeople截然不同找爱。
pageSize = 1
pageSize = 20
看出問(wèn)題來(lái)了嗎? 現(xiàn)在總記錄數(shù)是19條泡孩,當(dāng)pageSize = 20的時(shí)候车摄,該學(xué)生所有的家長(zhǎng)都包含在了agedPeople中了。但是pageSize= 1的時(shí)候仑鸥,僅僅包含了第2條記錄對(duì)應(yīng)的家長(zhǎng)吮播。
-
總結(jié)一下問(wèn)題就是:我告訴你我要查第i到j(luò)個(gè)學(xué)生的信息,你卻給了我數(shù)據(jù)庫(kù)第i到j(luò)條記錄封裝成的學(xué)生信息眼俊。我想要知道我一共有多少個(gè)學(xué)生符合查詢條件意狠,你卻告訴我一共有多少條記錄滿足查詢條件。這完全是兩碼事嘛疮胖。
問(wèn)題的解決辦法:避免在使用分頁(yè)插件的時(shí)候用嵌套的resultMap环戈。 如resultMap中去嵌套1個(gè)List闷板。
目前可以解決的辦法有兩種:
- 1.使用子查詢:
<resultMap id="UserBasicInfoToMap" type="com.cloud.fmmp.base.bean.to.UserBasicInfoTo">
<result column="FB_Name" property="fbName"></result>
<result column="FA_ID" property="faId"></result>
<result column="FA_NickName" property="faNickname"></result>
<result column="FB_Phone" property="fbPhone"></result>
<!--注意看這里,里面包含一個(gè)select子查詢-->
<collection property="agedPeople" javaType="java.util.List" ofType="java.lang.String" column="fa_id = fa_id" select="selectAgedPeople"></collection>
</resultMap>
<select id="getUserBasicInfoTos" resultMap="UserBasicInfoToMap">
SELECT FB_Name, FB_Phone, fb.FA_ID, fa.FA_NickName FROM ins_family_base fb
LEFT JOIN ins_family_account fa
ON fb.FA_ID = fa.FA_ID
WHERE fb.SYS_ID = #{sysId}
<if test="fbName != null">
and fb.FB_Name = #{fbName}
</if>
<if test="fbPhone != null">
and fb.FB_Phone = #{fbPhone}
</if>
</select>
<select id="selectAgedPeople" resultType="java.lang.String">
select PB_CNName from ins_family_people where fa_id = #{fa_id}
</select>
mapper接口方法院塞、封裝數(shù)據(jù)使用的javaBean也相同遮晚。唯一不同的就是select和resultMap了(見(jiàn)上)。
List<UserBasicInfoTo> getUserBasicInfoTos(@Param("sysId") String sysId, @Param("fbName") String fbName, @Param("fbPhone") String fbPhone);
此時(shí)pageNum=2, pageSize =1的結(jié)果:由于對(duì)學(xué)生進(jìn)行了過(guò)濾拦止,共有兩個(gè)符合條件的學(xué)生县遣,當(dāng)前頁(yè)顯示其中一個(gè)人的數(shù)據(jù),學(xué)生的家長(zhǎng)數(shù)據(jù)完整汹族。
- 2.分步查詢, 先將包含rowKey的主體信息查出來(lái)鞠抑,即先去主表查出能區(qū)分不同實(shí)體的屬性。然后根據(jù)主體屬性與嵌套的屬性關(guān)系忌警,查嵌套屬性并自己在java代碼內(nèi)完成封裝搁拙。這種方式很容易理解,就不羅列代碼了法绵。