原贴地址:https://www.cnblogs.com/nick-guo-sdly/p/7668462.html
最近开发中遇到了很多树形结构数据的需要,利用mybatis提供嵌套查询功能,基本上可以完美解决,但是对于其中的原理并不理解,导致在使用的时候像瞎猫碰死耗子一样,照着先前成功的例子copy,后来遇到了莫名奇怪的报错迟迟不能解决,于是百度了一番,大致了解了背后的原理,整理如下。
以简单的角色-菜单为例
表结构
其中menu为菜单表,role为角色表,roleandmenu是中间表,角色和菜单为多对多的关系,现在我们需要下图所示的实体类
1 import java.util.List; 2 3 public class RoleInfo { 4 5 private Integer roleid; 6 private String rolename; 7 private List
第一种方法:利用嵌套语句查询
12 8 9 12 133 4 5 6 7
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 }
结果:
原理如下:
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的值为多个键值对,如下图
此时向关联查询传递了两个参数id和name,此时还应该将关联的查询的parameterType改为java.util.Map,否则关联查询无法接受参数
2.在进行单一类型树形结构查询的时候,需要注意关联查询的结果集中的列是否有作为查询条件的列
这样说可能比较别扭,以上面的menu表为例,有一个parent列用于存储父部门的ID,使用嵌套查询获取以下实体类
1 import java.util.List; 2 3 public class MenuTree { 4 5 private Integer id; 6 private String name; 7 private Listchildren; 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 getChildren() { 21 return children; 22 } 23 public void setChildren(List children) { 24 this.children = children; 25 } 26 27 28 }
此时会产生一个问题:每个顶级菜单(parent为空的菜单)的子菜单只有一个查询结果,这是因为在关联查询getSubMenu中没有将id查询出来,而关联查询和主查询的resultMap一样,所以关联查询在映射结果集的时候就会再次去执行关联查询,而由于本次关联查询并没有取出id这个作为参数的属性,所以实际上只执行了N(结果集记录数)次关联查询。
因此,在这种情况中,必须在关联查询中查询出id这个列,否则会查询不出预期结果。
第二种方法:使用嵌套结果集
12 13 143 4 5 126 7 8 9 10 11
原理是通过关联查询,一次性将数据查询出来,然后根据resultMap的配置进行转换,构建目标实体类。
显然,这种方法更为直接,只需要访问一次数据库就可以了,不会造成严重的数据库访问消耗。
此外,我还发现了一个无解的情况,如果把嵌套结果集的返回值类型全部改成HashMap的话,会导致menulist里只有一行数据
解决的办法是,给collection标签的javaType赋值为目标集合类型
找了很久,终于在官方文档里找到了解释