MyBatis从入门到精通(十):使用association标签实现嵌套查询

最近在读刘增辉老师所著的《MyBatis从入门到精通》一书,颇有收获,因而将本身学习的过程以博客形式输出,若有错误,欢迎指正,如帮助到你,不胜荣幸!java

本篇博客主要讲解使用association标签实现嵌套查询的方法。git

1. 明确需求

仍然延用上篇博客中的需求:根据用户id查询用户信息的同时获取该用户的角色信息(假设一个员工只能拥有一个角色)。github

在上篇博客中,咱们分别使用了3种方式来实现这个需求,但这3个需求都有一个共同点,就是咱们使用了多表查询,即查询一次数据库就获取到咱们想要的全部数据。sql

有的同窗就说了,我不喜欢多表查询的方式,数据量大的时候会影响性能,这么简单的需求,我能够拆分红两步啊,第一步,先根据用户id查询出用户的信息和用户的角色id(仍然要关联表,只是由3张表关联减为了2张表关联),第二步,根据第一步查询出的角色id再去查询角色信息。数据库

这种方式固然能够,并且使用业务代码就能实现这个逻辑,不过本篇博客咱们不讲这种方式,而是经过association标签来实现。微信

2. 实现方式

由于咱们须要根据角色id查询角色的信息,因此咱们须要先在SysRoleMapper.xml中添加以下查询:mybatis

<select id="selectRoleById" resultMap="roleMap">
    SELECT * FROM sys_role WHERE id = #{id}
</select>
复制代码

这里的roleMap就是咱们在上篇博客中定义的,代码以下:app

<resultMap id="roleMap" type="com.zwwhnly.mybatisaction.model.SysRole">
    <id property="id" column="id"/>
    <result property="roleName" column="role_name"/>
    <result property="enabled" column="enabled"/>
    <result property="createBy" column="create_by"/>
    <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
</resultMap>
复制代码

而后,在接口SysUserMapper中添加以下方法:性能

/** * 根据用户id获取用户信息和用户的角色信息,嵌套查询方式 * * @param id * @return */
SysUserExtend selectUserAndRoleByIdSelect(Long id);
复制代码

接着,在对应的SysUserMapper.xml中添加以下代码:单元测试

<resultMap id="userRoleMapSelect" type="com.zwwhnly.mybatisaction.model.SysUserExtend" extends="sysUserMap">
    <association property="sysRole" select="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleById" column="{id=role_id}"/>
</resultMap>
复制代码
<select id="selectUserAndRoleByIdSelect" resultMap="userRoleMapSelect">
    SELECT  u.id,
            u.user_name,
            u.user_password,
            u.user_email,
            u.user_info,
            u.head_img,
            u.create_time,
            ur.role_id
    FROM sys_user u
    INNER JOIN sys_user_role ur ON u.id = ur.user_id
    WHERE u.id = #{id}
</select>
复制代码

能够发现,咱们给association标签添加了select="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleById",引用的就是咱们在SysRoleMapper.xml中定义的查询。

还添加了column="{id=role_id}",这里的id就是com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleById须要的参数名称,role_id是参数值,名称要和上面的查询中的最后一列保持一致。

注意事项:若是是多个参数的话,可使用column="{id=role_id,name=role_name}"这样的格式。

3. 单元测试

在SysUserMapperTest类中添加测试方法以下:

@Test
public void testSelectUserAndRoleByIdSelect() {
    SqlSession sqlSession = getSqlSession();

    try {
        SysUserMapper sysUserMapper = sqlSession.getMapper(SysUserMapper.class);

        SysUserExtend sysUserExtend = sysUserMapper.selectUserAndRoleByIdSelect(1001L);
        Assert.assertNotNull(sysUserExtend);

        Assert.assertNotNull(sysUserExtend.getSysRole());
    } finally {
        sqlSession.close();
    }
}
复制代码

运行测试代码,测试经过,输出日志以下:

DEBUG [main] - ==> Preparing: SELECT u.id, u.user_name, u.user_password, u.user_email, u.create_time, ur.role_id FROM sys_user u INNER JOIN sys_user_role ur ON u.id = ur.user_id WHERE u.id = ?

DEBUG [main] - ==> Parameters: 1001(Long)

TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, role_id

TRACE [main] - <== Row: 1001, test, 123456, test@mybatis.tk, 2019-06-27 18:21:07.0, 2

DEBUG [main] - ====> Preparing: SELECT * FROM sys_role WHERE id = ?

DEBUG [main] - ====> Parameters: 2(Long)

TRACE [main] - <==== Columns: id, role_name, enabled, create_by, create_time

TRACE [main] - <==== Row: 2, 普通用户, 1, 1, 2019-06-27 18:21:12.0

DEBUG [main] - <==== Total: 1

DEBUG [main] - <== Total: 1

从日志能够看出,分别执行了2次查询,查询了两次数据库。

4. 延迟加载

有的同窗可能会说,返回的角色信息我不必定用啊,每次都查询一次数据库,好浪费性能啊,能不能在我使用到角色信息即获取sysRole属性时再去查询数据呢?答案固然是能,那么如何实现呢?

实现延迟加载须要使用association标签的fetchType属性,该属性有lazy和eager两个值,分别表明延迟加载和积极加载。

因此上面的配置就要修改为:

<association property="sysRole" fetchType="lazy" select="com.zwwhnly.mybatisaction.mapper.SysRoleMapper.selectRoleById" column="{id=role_id}"/>
复制代码

为了能看到效果,咱们在测试方法中添加一行输出语句:

System.out.println("调用sysUserExtend.getSysRole()");
Assert.assertNotNull(sysUserExtend.getSysRole());
复制代码

再次运行测试方法,发现输出日志和预期的不同,在获取sysRole属性前仍是查询了2次数据库,这是为何呢?

这是由于MyBatis的全局配置中,有一个aggressiveLazyLoading参数,若是这个参数的值为ture,会使带有延迟加载属性的对象完整加载,若是为false,则会按需加载,这个参数默认值为ture(3.4.5版本开始默认值改成false),而截止目前,咱们使用的版本为3.3.1。

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.3.1</version>
</dependency>
复制代码

因此咱们要在mybatis-config.xml中添加以下配置:

<settings>
    <!--其余配置-->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
复制代码

再次运行测试方法,发现输出日志和预期的同样了:

DEBUG [main] - ==> Preparing: SELECT u.id, u.user_name, u.user_password, u.user_email, u.create_time, ur.role_id FROM sys_user u INNER JOIN sys_user_role ur ON u.id = ur.user_id WHERE u.id = ?

DEBUG [main] - ==> Parameters: 1001(Long)

TRACE [main] - <== Columns: id, user_name, user_password, user_email, create_time, role_id

TRACE [main] - <== Row: 1001, test, 123456, test@mybatis.tk, 2019-06-27 18:21:07.0, 2

DEBUG [main] - <== Total: 1

调用sysUserExtend.getSysRole()

DEBUG [main] - ==> Preparing: SELECT * FROM sys_role WHERE id = ?

DEBUG [main] - ==> Parameters: 2(Long)

TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time

TRACE [main] - <== Row: 2, 普通用户, 1, 1, 2019-06-27 18:21:12.0

DEBUG [main] - <== Total: 1

有的同窗可能又会说,你如今把全局的aggressiveLazyLoading改成了false,我能不能在触发某个方法时将全部的数据都加载进来呢?答案固然是能(否则怎么往下写,哈哈),那么如何实现呢?

MyBatis提供了参数lazyLoadTriggerMethods,这个参数的含义是,当调用配置中的方法时,加载所有的延迟加载数据,默认值为“equals,clone,hashCode,toString”。

简单修改下测试方法的代码:

System.out.println("调用sysUserExtend.equals(null)");
sysUserExtend.equals(null);

System.out.println("调用sysUserExtend.getSysRole()");
Assert.assertNotNull(sysUserExtend.getSysRole());
复制代码

再次运行测试方法,输出的部分日志以下:

调用sysUserExtend.equals(null)

DEBUG [main] - ==> Preparing: SELECT * FROM sys_role WHERE id = ?

DEBUG [main] - ==> Parameters: 2(Long)

TRACE [main] - <== Columns: id, role_name, enabled, create_by, create_time

TRACE [main] - <== Row: 2, 普通用户, 1, 1, 2019-06-27 18:21:12.0

DEBUG [main] - <== Total: 1

调用sysUserExtend.getSysRole()

从日志能够看出,调用equals方法后,就触发了延迟加载属性的查询。

5. 总结

使用association标签实现嵌套查询,用到的属性总结以下:

1)select:另外一个映射查询的id,MyBatis会额外执行这个查询获取嵌套对象的结果。

2)column:将主查询中列的结果做为嵌套查询的参数,配置方式如column="{prop1=col1,prop2=col2}",prop1和prop2将做为嵌套查询的参数。

3)fetchType:数据加载方式,可选值为lazy和eager,分别为延迟加载和积极加载。

4)若是要使用延迟加载,除了将fetchType设置为lazy,还须要注意全局配置aggressiveLazyLoading的值应该为false。这个参数在3.4.5版本以前默认值为ture,从3.4.5版本开始默认值改成false。

5)MyBatis提供的lazyLoadTriggerMethods参数,支持在触发某方法时直接触发延迟加载属性的查询,如equals()方法。

6. 源码及参考

源码地址:github.com/zwwhnly/myb…,欢迎下载。

刘增辉《MyBatis从入门到精通》

7. 最后

打个小广告,欢迎扫码关注微信公众号:「申城异乡人」,按期分享Java技术干货,让咱们一块儿进步。

相关文章
相关标签/搜索