【mybatis】之二:级联查询与懒加载

一、用法提前看

场景假设:有A、B两个对象,在A对象中引用了B对象。Java层面上,通俗来说就是通过A对象的getter方法可以拿到B对象的引用。数据库层面,其实就是两表的关联查询。对于这种情况,大概有以下三种编写方式(都是使用resultMap进行结果集映射)。

  1. 不使用association标签,result标签的property属性使用A.B的形式;
  2. 使用association标签,并将其property属性的值设为A对象中为B对象指定的名称,添加javaType属性,将其值设为B的全限定名称,最后在association的子标签中完成对B对象的映射;
  3. 使用association标签,并将其property属性的值设为A对象中为B对象指定的名称,添加select属性,将其值设为查询B方法的全限定名称,column属性对应字段的值将作为查询B方法的参数,最后在association的子标签中完成对B对象的映射。

看完以上描述,你可能觉得有些抽象或者难以理解,没关系,结合下面的代码可以很好的理解这几种不同的编写方式。

二、用法示例

本节示例沿用上一章的Employee实体及相关的mapper等,新增一张业务表tbl_department用来表示员工的部门。下面是这张表的结构,java层面的相关类和配置文件会在下面具体说明。

图片.png
  • 不使用association标签的方式

使用这种方式,写一条sql语句,比较直接

sql部分:

    <select id="getEmployeeWithDeptById0" resultMap="employeeDept0">
        select e.id id, e.name name, e.gender gender, e.email email, e.d_id departmentId, d.department_name departmentName
        from tbl_employee e, tbl_department d
        where d.id=e.d_id and e.id=#{id}
    </select>

对应的resultMap如下,可以看到这种写法比较清爽和直接:

    <!-- 第一种:利用resultMap进行级联查询,不使用association标签-->
    <resultMap id="employeeDept0" type="com.hly.entity.Employee">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="gender" property="gender"></result>
        <result column="email" property="email"></result>
        <result column="departmentId" property="department.id"></result>
        <result column="departmentName" property="department.departmentName"></result>
    </resultMap>
  • 使用association标签+javaType属性
    这种方法个人感觉跟第一种没有本质上的区别,还是一条sql语句对两张表进行关联查询,只不过在结果集映射的时候有一些不同,引入了association标签。可读性比较好,对象的结构关系相较于第一种方式来说更为清晰和明朗。
    sql部分,与第一种无异:
    <select id="getEmployeeWithDeptById" resultMap="employeeDept">
        select e.id id, e.name name, e.gender gender, e.email email, e.d_id departmentId, d.department_name departmentName
        from tbl_employee e, tbl_department d
        where d.id=e.d_id and e.id=#{id}
    </select>

resultMap在进行结果映射时,有一定的区别:

    <!-- 第二种:利用resultMap进行级联查询,使用association标签 -->
    <resultMap id="employeeDept" type="com.hly.entity.Employee">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="gender" property="gender"></result>
        <result column="email" property="email"></result>
        <association property="department" javaType="com.hly.entity.Department">
            <id column="departmentId" property="id"></id>
            <result column="departmentName" property="departmentName"></result>
        </association>
    </resultMap>

可以看到在这种写法中,通过association标签明确指定了department对象的类型,然后在这个association的子标签中对department对象进行结果映射。

  • 使用association标签+select属性
    这种方法就有意思了。与前面两种写法有比较大的不同,使用association的select标签,可以将原本两表联查的一条sql语句拆分为两条简单的sql语句。个人以为搞出这种方式的原因就是要支持级联查询的懒加载吧,这样可以很好的提升数据库的性能,毕竟只有在用到关联对象相关属性的时候,才会执行第二步的查询操作。这部分内容等到后面了解其原理,看过源码后再回来详细说明,在此留一个根。
    sql部分,这里就分两部分了。第一是在tbl_employee表中,根据id查出对应的记录。第二步就是根据前一步中查出的d_id的值,在tbl_department中查询对应的记录。注意这两个sql是分散在两个mapper.xml中的哈。
    sql1,员工的查询:
    <select id="getEmployeeWithDeptById0" resultMap="employeeDept0">
        select e.id id, e.name name, e.gender gender, e.email email, e.d_id departmentId, d.department_name departmentName
        from tbl_employee e, tbl_department d
        where d.id=e.d_id and e.id=#{id}
    </select>

sql2,部门的查询:

    <select id="getDeptById" resultType="com.hly.entity.Department">
        SELECT id id, department_name departmentName FROM tbl_department where id=#{id}
    </select>

这种方式下,对结果集的映射:

    <!-- 第三种:利用resultMap进行分步查询 -->
    <resultMap id="employeeByStep" type="com.hly.entity.Employee">
        <id column="id" property="id"></id>
        <result column="name" property="name"></result>
        <result column="gender" property="gender"></result>
        <result column="email" property="email"></result>
        <association property="department" select="com.hly.dao.DepartmentMapper.getDeptById" column="d_id">
            <id column="id" property="id"></id>
            <result column="department_name" property="departmentName"></result>
        </association>
    </resultMap>

association标签中有两个重要的属性,select是用来指定这个对象怎么去查,而column属性则是从第一步的查询结果中找出select所需的查询参数。

三、效果查看

本节省略了mapper接口类以及@Test方法的展示,如果需要看,可移步github瞅一眼,没什么好说的。

  • 第一种&第二种
DEBUG [main] - ==>  Preparing: select e.id id, e.name name, e.gender gender, e.email email, e.d_id departmentId, d.department_name departmentName from tbl_employee e, tbl_department d where d.id=e.d_id and e.id=? 
DEBUG [main] - ==> Parameters: 4(Integer)
DEBUG [main] - <==      Total: 1
Employee{id=4, name='John He1122', gender='1', email='1@webank.com'}
Department{id=1, departmentName='开发部'}

控制台打印的信息如上,执行了一条sql语句,输入一个Integer类型参数,得到一条结果。下面两行是打印的查询结果,员工信息和部门信息都打印出来了,没什么毛病。

  • 第三种
DEBUG [main] - ==>  Preparing: SELECT id,name,gender,email,d_id FROM tbl_employee where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - ====>  Preparing: SELECT id id, department_name departmentName FROM tbl_department where id=? 
DEBUG [main] - ====> Parameters: 1(Integer)
DEBUG [main] - <====      Total: 1
DEBUG [main] - <==      Total: 1
Lingyu He
开发部

控制台的输出也比较直观,执行了两条语句,各查出一条记录。最后两行分别打印的是e.getNmae(),e.getDepartment().getDepartmentName()。

四、懒加载

我用自己的话描述下懒加载的感念。懒加载也叫做延迟加载,可以理解为数据并不是随着动作的执行而立刻被加载(或查询),而是等到真正需要使用到这部分数据时,才会执行对数据的加载(或查询)。

mybatis的社区应该是非常优秀,官方文档提供了多语言的支持,当然不乏简体中文,这是很赞的。但是,本着学习英语的美好愿景,还是看一下英文文档里,对懒加载内容相关的说明吧。
全局配置文件中,有两个跟懒加载机制息息相关的参数,对执行模块的懒加载行为有着决定性的作用。

setting : lazyLoadingEnabled
description : Globally enables or disables lazy loading. When enabled, all relations will be lazily loaded. This value can be superseded for an specific relation by using the fetchType attribute on it.
valid values : true | false
default : false

这个东西呢就是说,在全局配置文件里,Settings这个配置标签中,有一个很重要的配置项叫做lazyLoadingEnabled。从名字就可以看出这是一个开关项,用来控制全局的懒加载功能。同时,描述中也说了,这个值可以通过在特定的级联查询上通过设置fetchType的方式,对全局的设值做一个局部覆盖。取值范围就是true或者是false,不设置的话默认是false。

setting : aggressiveLazyLoading
description : When enabled, any method call will load all the lazy properties of the object. Otherwise, each property is loaded on demand (see also lazyLoadTriggerMethods)
valid values : true | false
default : false (true in ≤3.4.1)

这个设置项的意思是说,当我是true的时候,一个对象中任意一个方法的调用,都会引起该对象所有延迟加载对象的加载;如果为false的话,那么就还是按需加载。这个aggressive的命名,意味深长呀。。。
具体的使用示例在下一小节里展示。

五、懒加载后续

先来看一下对上一小节两个配置的应用效果吧。

    <settings>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="true"></setting>
    </settings>

先附上这个@Test方法的代码(也就是前面所说的第三种实现方式的test方法):

    @Test
    public void testAssociationByStep(){
        SqlSession sqlSession = null;
        try {
            sqlSession = getSession();
            EmployeeDeptMapper employeeMapper = sqlSession.getMapper(EmployeeDeptMapper.class);
            Employee e = employeeMapper.getEmpByStep(1);
            System.out.println(e.getName());
            System.out.println(e.getDepartment().getDepartmentName());
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            sqlSession.close();
        }
    }

还记得之前没加两个配置项时候的输出步?我们再来回顾一下:

DEBUG [main] - ==>  Preparing: SELECT id,name,gender,email,d_id FROM tbl_employee where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - ====>  Preparing: SELECT id id, department_name departmentName FROM tbl_department where id=? 
DEBUG [main] - ====> Parameters: 1(Integer)
DEBUG [main] - <====      Total: 1
DEBUG [main] - <==      Total: 1
Lingyu He
开发部

可以看到,把两条语句都执行了,拿到结果后,打印了日志。再来看看加了两个配置项之后的输出。

DEBUG [main] - ==>  Preparing: SELECT id,name,gender,email,d_id FROM tbl_employee where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Lingyu He
DEBUG [main] - ==>  Preparing: SELECT id id, department_name departmentName FROM tbl_department where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
开发部

有木有发现细微的不同?没错,在两条sql语句之间打印了员工的name,后面因为调用了员工的getDepartment()方法,才又执行了第二条语句,查出了部门名称,是否看的出懒加载的特性了?如果觉得不明显,没关系,我们进一步进行修改。直接把@Test中对getDepartment的调用注释调,然后再运行一次观察结果:

DEBUG [main] - ==>  Preparing: SELECT id,name,gender,email,d_id FROM tbl_employee where id=? 
DEBUG [main] - ==> Parameters: 1(Integer)
DEBUG [main] - <==      Total: 1
Lingyu He

这下好了,只执行了一条语句,并没有去查询员工的部门信息。这下就完美的证实了懒加载的特性。关于另外一个配置项的使用效果,也可以很简单的通过修改部分代码来验证,不赘述。

六、问题留根

问题是这样的,上一节的@Test代码中,如果第一个输出语句,我们那不打印name,而是直接打印Employee这个对象,会发现懒加载失败。

sout语句在打印一个对象时,会去调这个对象的toString方法。此时作者认为如果再toString中不引用department对象的内容,应该就不会破坏懒加载的条件,然而事实是,依然不会懒加载。
那么这里自然而然就抛出一个问题,既然懒加载是按需加载,那么这个需到底是什么时候,是什么东西,也就是说加载的时机和条件是如何定义和设置的。mybatis里面肯定会有相关的设置和触发加载的定义,现在抛出这个问题,后面看到弄懂了再回来补上。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容