iBatis resultMap groupBy属性使用心得[转载]
转载地址:http://www.falaosao.net/article.asp?id=440
其他参考地址:http://opensource.atlassian.com/confluence/oss/display/IBATIS/How+do+I+get+around+the+N+Plus+1+selects+problem
最近开始转用j2ee做开发,使用的数据持久层是iBatis。
在iBatis中要解决1:N、M:N问题必须要使用到resultMap,但使用过程中会遇到一个问题,就是groupBy的不能继承问题。现在举个例子向大家介绍一下这个问题和解决方法。
问题描述:
考虑一下这种情况,假如一家公司为了员工福利决定搞业余运动俱乐部,员工可以自由报名。运动的种类根据部门决定,这样就有了四个实体,公司,部门,员工和运动。而它们的关系是,公司:部门,部门:员工都是1:N关系,而部门对运动是N:M关系。
如果我们要用一条sql语句做一个如下图的报名页面,应该怎么实现呢?
首先我们必须定义resultMap,代码如下:
<resultMap id="DepartmentResult" class="departVo" groupby="deptID">
<result property="deptID" column="deptID"/>
<result property="deptName" column="deptName"/>
<result property="staffList" resultMap="StaffResult"/>
<result property="sportList" resultMap="SportResult"/>
</resultMap>
<resultMap id="StaffResult" class="staffVo" groupBy="staffID">
<result property="staffID" column="staffID"/>
<result property="staffName" column="staffName"/>
</resultMap>
<resultMap id="SportResult" class="SportVo" groupby="SportID">
<result property="SportID" column="SportID"/>
<result property="SportName" column="SportName"/>
</resultMap>
<statement id="getEverything" resultMap="DepartmentResult">
Select
*
FROM
tbDepartment, tbStaff, tbResult, tbDepSport
Where
tbStaff.deptid = tbDepartment.deptID
AND
tbSport.sportid = tbDepSport.sportid
AND
tbDepartment.deptID = tbDepSport.deptID
</statement>
其中tbDepSport是一个mapping表,反映部门和运动种类的关系。
执行以上代码出来,我们会得到一个比较以外的结果,如下图:
问题分析:
为什么会有这种问题,问题的关键在于属性groupBy。
首先我们先分析一下执行以上sql语句所出来的结果集:
开发支持部 | 员工甲 | 足球 |
开发支持部 | 员工甲 | 篮球 |
开发支持部 | 员工甲 | 羽毛球 |
开发支持部 | 员工乙 | 足球 |
开发支持部 | 员工乙 | 篮球 |
开发支持部 | 员工乙 | 羽毛球 |
开发支持部 | 员工丙 | 足球 |
开发支持部 | 员工丙 | 篮球 |
开发支持部 | 员工丙 | 羽毛球 |
开发支持部 | 员工丁 | 足球 |
开发支持部 | 员工丁 | 篮球 |
开发支持部 | 员工丁 | 羽毛球 |
开发支持部 | 员工卯 | 足球 |
开发支持部 | 员工卯 | 篮球 |
开发支持部 | 员工卯 | 羽毛球 |
基础架构部 | 员工己 | 足球 |
基础架构部 | 员工己 | 篮球 |
基础架构部 | 员工己 | 羽毛球 |
基础架构部 | 员工庚 | 足球 |
基础架构部 | 员工庚 | 篮球 |
基础架构部 | 员工庚 | 羽毛球 |
基础架构部 | 员工申 | 足球 |
基础架构部 | 员工申 | 篮球 |
基础架构部 | 员工申 | 羽毛球 |
基础架构部 | 员工壬 | 足球 |
基础架构部 | 员工壬 | 篮球 |
基础架构部 | 员工壬 | 羽毛球 |
基础架构部 | 员工葵 | 足球 |
基础架构部 | 员工葵 | 篮球 |
基础架构部 | 员工葵 | 羽毛球 |
这是一个笛卡尔积的结果,有很多冗余的结果,resultMap是怎么处理这个结果集的?
首先当程序执行id为getEverything的statement时,先找到resultMap:DepartmentResult
由于DepartmentResult有groupBy关键字,因此DepartmentResult会根据groupBy的值过滤掉重复的结果,并映射到departVo里的相关属性,然后放在一个List对象里面,最后成功筛选出两个部门。
在处理DepartmentResult中字段的同时找到了resultMap:StaffResult,发现StaffResult有groupBy关键字,同理又根据groupBy的值过滤掉重复的字段,这里由于两个部门的员工都不一样,因此过滤重复的结果后就能得到每个部门的员工列表。
最后处理resultMap:StaffResult,同样的道理会过滤掉相同的结果。如果两个部门的运动是不一致的,那么我们很轻松就能得到两个运动的结果集。但假如两个部门的运动是一样的,由于groupBy的关系,最后我们只能得到一个运动结果集,就是上图的结果。
解决办法:
其实我觉得这是ibatis的一个bug,如果resultMap的groupBy能向上继承,那么 StaffResult的groupBy实际上就是deptID和sportID,那么就不会出现这种情况。因此要解决这个问题,我们必须要在 StaffResult中引入deptID,并且修改groupBy属性。代码如下:
<resultMap id="SportResult" class="SportVo" groupby="deptID,SportID">
<result property="deptID" column="deptID"/>
<result property="SportID" column="SportID"/>
<result property="SportName" column="SportName"/>
</resultMap>
同时为了ibatis能正常映射,我们还要在SportVo中增加deptID属性。
问题解决了,但破坏了SportVo的独立性,希望ibatis能关注解决这个问题,如果大家有更好的解决办法,也欢迎留言给我。