mybatis查询树形数据的两种方法

时间:2024-03-24 20:06:36

原贴地址:https://www.cnblogs.com/nick-guo-sdly/p/7668462.html

最近开发中遇到了很多树形结构数据的需要,利用mybatis提供嵌套查询功能,基本上可以完美解决,但是对于其中的原理并不理解,导致在使用的时候像瞎猫碰死耗子一样,照着先前成功的例子copy,后来遇到了莫名奇怪的报错迟迟不能解决,于是百度了一番,大致了解了背后的原理,整理如下。

  以简单的角色-菜单为例

 表结构

mybatis查询树形数据的两种方法

 

其中menu为菜单表,role为角色表,roleandmenu是中间表,角色和菜单为多对多的关系,现在我们需要下图所示的实体类

mybatis查询树形数据的两种方法

 1 import java.util.List;
 2 
 3 public class RoleInfo {
 4     
 5     private Integer roleid;
 6     private String rolename;
 7     private List<Menu> menulist;
 8     public Integer getRoleid() {
 9         return roleid;
10     }
11     public void setRoleid(Integer roleid) {
12         this.roleid = roleid;
13     }
14     public String getRolename() {
15         return rolename;
16     }
17     public void setRolename(String rolename) {
18         this.rolename = rolename;
19     }
20     public List<Menu> getMenulist() {
21         return menulist;
22     }
23     public void setMenulist(List<Menu> menulist) {
24         this.menulist = menulist;
25     }
26     
27     @Override
28     public String toString() {
29         return "RoleInfo [roleid=" + roleid + ", rolename=" + rolename + ", menulist=" + menulist + "]";
30     }
31     
32 }

mybatis查询树形数据的两种方法

 

 第一种方法:利用嵌套语句查询

mybatis查询树形数据的两种方法

 1 <resultMap type="com.test.mybatis.model.RoleInfo" id="roleModel">
 2         <id column="id" property="roleid"/>
 3         <result column="name" property="rolename"/>
 4         <collection property="menulist" select="getMenu" column="id">
 5             
 6         </collection>
 7     </resultMap>
 8     
 9     <select id="getRoleInfo" resultMap="roleModel">
10         select id,name from role
11     </select>
12     
13     <select id="getMenu" resultType="com.test.mybatis.model.Menu">
14         select m.id,m.name 
15         from menu m join roleandmenu ram on m.id=ram.menuId
16         where ram.roleId=#{id}
17     </select>

mybatis查询树形数据的两种方法

mybatis查询树形数据的两种方法

 1     @Test
 2     public void testRoleAndMenu() throws IOException {
 4         Reader reader = Resources.getResourceAsReader("mybatis.xml");
 5         SqlSessionFactory sqlsessionfac = new SqlSessionFactoryBuilder().build(reader);
 6         SqlSession sqlsession = sqlsessionfac.openSession();
 7         try {
 8             RoleAndMenuMapper mapper = sqlsession.getMapper(RoleAndMenuMapper.class);
 9             System.out.println(JSONObject.toJSON(mapper.getRoleInfo()));
10         } catch (Exception e) {
11             // TODO: handle exception
12             e.printStackTrace();
13         } finally {
14             sqlsession.close();
15         }
17     }

mybatis查询树形数据的两种方法

结果:

 mybatis查询树形数据的两种方法

原理如下:

  1.mybatis先执行getRoleInfo这个查询,获取结果集

  2.从ResultSet中逐一取出记录,构建RoleInfo对象并为映射属性赋值

  3.赋值过程中发现目标menulist属性配置了一个关联集合(collection),此时执行id为collection标签中select属性值(getMenu)的查询,并将当前记录中的id属性作为此查询的参数。(association标签同理)

  4.将关联查询返回的结果映射到meunlist属性

  5.执行步骤2,直至ResultSet.next=false

  6.返回查询结果

  这种方式的好处在于简单易懂,通过简单的配置就可以达到目标效果。不足之处在于如果结果集记录条数过大,会造成较大的数据库访问消耗,因为在从ResultSet中取出记录的时候每取一条,便执行一次关联查询,假设一次查询的结果集有10条记录,则数据库的访问次数为:关联查询次数(10)+返回结果集的查询(1)=11次。

需要注意的地方

 1.collection/association标签的column属性:当向关联查询传递的参数个数为1时,column的值应为结果集中的列名,而不是映射属性名(property),上面的例子中,向关联查询传递id值,column的值应为id而不是roleid。

  可以向关联查询传递多个参数,此时column的值为多个键值对,如下图

mybatis查询树形数据的两种方法

  此时向关联查询传递了两个参数id和name,此时还应该将关联的查询的parameterType改为java.util.Map,否则关联查询无法接受参数

mybatis查询树形数据的两种方法

 

  2.在进行单一类型树形结构查询的时候,需要注意关联查询的结果集中的列是否有作为查询条件的列

  这样说可能比较别扭,以上面的menu表为例,有一个parent列用于存储父部门的ID,使用嵌套查询获取以下实体类

mybatis查询树形数据的两种方法

 1 import java.util.List;
 2 
 3 public class MenuTree {
 4     
 5     private Integer id;
 6     private String name;
 7     private List<Menu> children;
 8     public Integer getId() {
 9         return id;
10     }
11     public void setId(Integer id) {
12         this.id = id;
13     }
14     public String getName() {
15         return name;
16     }
17     public void setName(String name) {
18         this.name = name;
19     }
20     public List<Menu> getChildren() {
21         return children;
22     }
23     public void setChildren(List<Menu> children) {
24         this.children = children;
25     }
26     
27     
28 }

mybatis查询树形数据的两种方法

mybatis查询树形数据的两种方法

  此时会产生一个问题:每个*菜单(parent为空的菜单)的子菜单只有一个查询结果,这是因为在关联查询getSubMenu中没有将id查询出来,而关联查询和主查询的resultMap一样,所以关联查询在映射结果集的时候就会再次去执行关联查询,而由于本次关联查询并没有取出id这个作为参数的属性,所以实际上只执行了N(结果集记录数)次关联查询

  因此,在这种情况中,必须在关联查询中查询出id这个列,否则会查询不出预期结果。

mybatis查询树形数据的两种方法

mybatis查询树形数据的两种方法

 

 第二种方法:使用嵌套结果集

mybatis查询树形数据的两种方法

 1 <resultMap type="com.test.mybatis.model.RoleInfo" id="roleModel">
 2         <id column="id" property="roleid"/>
 3         <result column="name" property="rolename"/>
 4         <collection property="menulist" ofType="com.test.mybatis.model.Menu">
 5             <id column="menuid" property="id"/>
 6             <result column="menuname" property="name"/>
 7             <result column="description" property="description"/>
 8             <result column="parent" property="parent"/>
 9             <result column="createdate" property="createdate"/>
10             <result column="modifydate" property="modifydate"/>
11         </collection>
12     </resultMap>
13     
14     <select id="getRoleInfo" resultMap="roleModel">
15         select 
16             ram.roleid as id,
17             ro.name as name,
18             me.id as menuid,
19             me.name as menuname,
20             me.description,
21             me.parent,
22             me.createdate,
23             me.modifydate 
24         from roleandmenu ram
25             left outer join role ro on ram.roleid=ro.id
26             left outer join menu me on ram.menuid=me.id
27     </select>

mybatis查询树形数据的两种方法

  原理是通过关联查询,一次性将数据查询出来,然后根据resultMap的配置进行转换,构建目标实体类。

  显然,这种方法更为直接,只需要访问一次数据库就可以了,不会造成严重的数据库访问消耗。

   此外,我还发现了一个无解的情况,如果把嵌套结果集的返回值类型全部改成HashMap的话,会导致menulist里只有一行数据

  mybatis查询树形数据的两种方法

  解决的办法是,给collection标签的javaType赋值为目标集合类型

  mybatis查询树形数据的两种方法

  找了很久,终于在官方文档里找到了解释

  mybatis查询树形数据的两种方法