mybatis嵌套子查询

mybatis提供了一种机制,叫做嵌套语句查询,可以大大简化上述的操作,加入配置及代码如下:

<resultMap type="domain.User" id="user">
  <id column="id" property="id"/>
  <result column="age" property="age"/>
  <collection column="id" property="orders" ofType="domain.User_orders"
   select="selectOrderByUser"> 
   <id column="id" property="id"/>
  <result column="name" property="name"/>
 </collection>
</resultMap>
<select id="selectOrderByUser" parameterType="integer" resultType="domain.User_orders">
   select id,name from user_orders where user_id = #{id}
</select>
<select id="findById" resultMap="user" parameterType="integer">
         select * from user where id = #{id}
    </select>  

测试(可以成功查询到所有信息):

String config = "sqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(config);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
// 执行在bean配置文件中定义的sql语句
User user = session.selectOne("UserMapper.findById", 1);
//一句即可获取到复杂的User对象。
System.out.println(user);
session.commit();
session.close();  

嵌套语句查询的原理
在上面的代码中,Mybatis会执行以下流程:

  • 先执行 findById 对应的语句从User表里获取到ResultSet结果集;
  • 取出ResultSet下一条有效记录,然后根据resultMap定义的映射规格,通过这条记录的数据来构建对应的一个User 对象。
    当要对User中的orders属性进行赋值的时候,发现有一个关联的查询,此时Mybatis会先执行这个select查询语句,得到返回的结果,将结果设置到user的orders属性上
    这种关联的嵌套查询,有一个非常好的作用就是:可以重用select语句,通过简单的select语句之间的组合来构造复杂的对象。想如上的两个select完全可以独立使用。
  • 嵌套查询的多对一
    上面的关联查询查询其实是对于一对多的查询,即从user中查出user_order的信息。
    现在从user_order中查user的信息,在User_order表中增加字段user:

    public class User_orders {
     private int id;
     private String name;
     private User user;
     //xxx
    

    配置select:

    <resultMap type="domain.User_orders" id="user_order">
      <id column="id" property="id"/>
      <result column="name" property="name"/>
        <association property="user" column="user_id" javaType="domain.User" select="selectUserByOrderId">
           <id column="id" property="id"/>
         <result column="age" property="age"/>
        </association>
    </resultMap>
     <select id="selectUserByOrderId" parameterType="INTEGER" resultType="domain.User">
         select id,age from user where id = #{id}
     </select>
        <select id="findOne" resultMap="user_order" parameterType="integer">
           select * from  user_orders where id=#{id}
        </select>  
    
    SqlSession session = sqlSessionFactory.openSession();
            // 执行在bean配置文件中定义的sql语句
            User_orders user_orders= session.selectOne("User_ordersMapper.findOne", 1);
            System.out.println(user_orders);
            //查询到了user_order对应的user的信息
            session.commit();
            session.close();  
    

    嵌套查询的N+1问题
    尽管嵌套查询大量的简化了存在关联关系的查询,但它的弊端也比较明显:即所谓的N+1问题。关联的嵌套查询显示得到一个结果集,然后根据这个结果集的每一条记录进行关联查询。
    现在假设嵌套查询就一个(即resultMap 内部就一个association标签),现查询的结果集返回条数为N,那么关联查询语句将会被执行N次,加上自身返回结果集查询1次,共需要访问数据库N+1次。如果N比较大的话,这样的数据库访问消耗是非常大的!所以使用这种嵌套语句查询的使用者一定要考虑慎重考虑,确保N值不会很大。
    以上面一对多(根据user的id查询order)的例子为例,select 语句本身会返回user条数为1 的结果集,由于它存在有1条关联的语句查询,它需要共访问数据库 1*(1+1)=2次数据库。

  •