AI 摘要

文章系统梳理 MyBatis SQL 映射文件结构与最佳实践,围绕条件查询、结果映射、增删改操作及缓存机制展开。详述单参数、实体、Map、多参数四种入参方式,对比 resultType 自动映射与 resultMap 手动映射,演示 association、collection 嵌套处理一对一、一对多关系,给出驼峰转换与自动映射级别配置,总结 insert、update、delete 写法及事务控制要点,并说明一级、二级缓存原理与局限性,为 SSM 项目提供完整持久层参考。

MyBatis 映射基础

SQL 映射文件概述

MyBatis 真正的特色在于 SQL 映射,功能强大,使用简单。

SQL 映射文件中的顶级元素:

名称描述
mapperSQL 映射文件的根元素
cache配置给定命名空间的缓存
cache-ref从其他命名空间引用缓存配置
resultMap用来描述数据库结果集和对象的对应关系
sql可以重用的 SQL 块,也可以被其他语句引用
insert映射插入语句
update映射更新语句
delete映射删除语句
select映射查询语句

编写 SQL 映射文件的注意事项:

  • SQL 映射文件与该 Mapper 接口同名(实体类名 + Mapper),并放置在同一包路径下。
  • 以映射的 Mapper 接口的完全限定类名(包路径 + 接口名)作为 SQL 映射文件 mapper 元素的 namespace 属性。
  • 接口中的方法名与 SQL 映射文件中映射 SQL 语句的元素的 id 属性一一对应。

MyBatis 条件查询

实现单一条件查询

在 MyBatis 中实现条件查询,如果仅传入一个基本数据类型或其包装类型或一个 String 类型的参数,MyBatis 框架的处理方式非常简单。

select 元素实现单一条件查询的基本用法:

// Mapper接口定义方法
... 方法名(参数类型 参数名);
<!-- SQL映射文件定义SQL语句 -->
<select id="方法名" resultType="返回值类型" parameterType="参数类型">
    ... WHERE xx = #{参数名}
</select>

使用 select 元素实现单一条件查询的注意事项:

  • parameterType 属性表示 SQL 语句传入的参数类型,可以使用全限定名或别名。
  • parameterType 属性指定参数类型为 string,是 java.lang.String 的别名,是 MyBatis 内建的类型别名。
  • 使用 #{...} 获取参数值时,MyBatis 会将其转换成 PreparedStatement 参数,并为其赋值。

示例:单一条件查询

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...

    /**
     * 根据真实姓名模糊查询用户列表
     *
     * @param realName 真实姓名
     * @return 用户列表
     */
    List<SysUser> getUserListByRealName(String realName);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <!--
        parameterType属性:传入SQL语句的参数类型
        单一条件查询,使用#{参数名}获取方法传入的参数
        SQL映射文件中实现模糊查询,需要使用CONCAT函数做字符串拼接
    -->
    <select id="getUserListByRealName" resultType="cn.duozai.entity.SysUser" parameterType="string">
        SELECT * FROM t_sys_user
        WHERE realName LIKE CONCAT('%', #{realName}, '%')
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据真实姓名模糊查询用户列表
     * @return void
     */
    @Test
    public void getUserListByRealName() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByRealName("张");

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


使用 Java 对象入参实现多条件查询

在 MyBatis 中实现多条件查询,可以将查询参数保存在实体类中,传入方法实现多条件查询。

select 元素使用 Java 对象入参实现多条件查询的基本用法:

// Mapper接口定义方法
... 方法名(实体类类型 对象名);
<!-- SQL映射文件定义SQL语句 -->
<select id="方法名" resultType="返回值类型" parameterType="实体类类型">
    ... WHERE xx = #{实体类中的属性名}
</select>

使用 Java 对象入参,灵活性欠佳,参数受对象结构即实体类属性的限制。


示例:使用 Java 对象入参实现多条件查询

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...

    /**
     * 根据条件(SysUser)查询用户列表
     *
     * @param sysUser 用户表实体类对象
     * @return 用户列表
     */
    List<SysUser> getUserListBySysUser(SysUser sysUser);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <!-- 多条件查询实体类入参,使用#{实体类属性名}获取方法传入的参数 -->
    <select id="getUserListBySysUser" resultType="cn.duozai.entity.SysUser" parameterType="cn.duozai.entity.SysUser">
        SELECT * FROM t_sys_user
        WHERE realName LIKE CONCAT('%', #{realName}, '%') AND roleId = #{roleId}
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据条件(SysUser)查询用户列表
     *
     * @return void
     */
    @Test
    public void getUserListBySysUser() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        // 封装SysUser查询条件
        SysUser searchSysUser = new SysUser();
        searchSysUser.setRealName("刘");
        searchSysUser.setRoleId(2);

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListBySysUser(searchSysUser);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


使用 Map 对象入参实现多条件查询

在 MyBatis 中实现多条件查询,可以将查询参数保存在 Map 对象中,传入方法实现多条件查询。

select 元素使用 Map 对象入参实现多条件查询的基本用法:

// Mapper接口定义方法
... 方法名(Map 对象名);
<!-- SQL映射文件定义SQL语句 -->
<select id="方法名" resultType="返回值类型" parameterType="map">
    ... WHERE xx = #{map-key}
</select>

使用 Map 对象入参,不受实体类设计的限制,可以灵活地传递所需要的参数。


示例:使用 Map 对象入参实现多条件查询

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...

    /**
     * 根据条件(Map)查询用户列表
     *
     * @param sysUserMap Map对象
     * @return 用户列表
     */
    List<SysUser> getUserListByMap(Map<String, Object> sysUserMap);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <!-- 多条件查询Map入参,使用#{Map-Key}获取方法传入的参数 -->
    <select id="getUserListByMap" resultType="cn.duozai.entity.SysUser" parameterType="map">
        SELECT * FROM t_sys_user
        WHERE realName LIKE CONCAT('%', #{rName}, '%') AND roleId = #{rId}
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据条件(Map)查询用户列表
     *
     * @return void
     */
    @Test
    public void getUserListByMap() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        // 封装SysUserMap查询条件
        Map<String, Object> sysUserMap = new HashMap<>();
        sysUserMap.put("rName", "刘");
        sysUserMap.put("rId", 2);

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByMap(sysUserMap);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


使用多参数入参实现多条件查询

MyBatis 允许直接对方法传入多个参数,以实现多参数条件查询。

select 元素使用多参数入参实现多条件查询的基本用法:

// Mapper接口定义方法
... 方法名(@Param("参数名1") 参数1, @Param("参数名2") 参数2, ...);
<!-- SQL映射文件定义SQL语句 -->
<select id="方法名" resultType="返回值类型">
    ... WHERE xx = #{@Param注解标记的参数名}
</select>

多参数入参,适合传递的参数的数量较少的简单场景。

使用 select 元素 + 多参数入参实现多条件查询的注意事项:

  • 在 MyBatis 底层实现中,默认会把传入的参数转换成 Map 格式,且使用 param + 参数在列表中的位置作为 map-key,如 param1、param2。
  • 通常情况下,在方法中传入多个参数时,会使用 @Param 注解为参数命名。
  • 多参数入参时,select 元素不需要指定 parameterType 属性。

示例:使用多参数入参实现多条件查询

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 根据条件(Params)查询用户列表
     *
     * @param realName 真实姓名
     * @param roleId 用户角色id
     * @return 用户列表
     */
    List<SysUser> getUserListByParams(@Param("realName") String realName, @Param("rId") Integer roleId);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <!--
        多参数入参,使用#{param+参数序号}获取方法传入的参数
        使用@Param注解给参数取别名时,使用使用#{参数别名}获取方法传入的参数
        多参数入参时,不需要指定parameterType属性
    -->
    <select id="getUserListByParams" resultType="cn.duozai.entity.SysUser">
        SELECT * FROM t_sys_user
        WHERE realName LIKE CONCAT('%', #{realName}, '%') AND roleId = #{rId}
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据条件(Params)查询用户列表
     *
     * @return void
     */
    @Test
    public void getUserListByParams() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByParams("刘", 2);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


MyBatis 结果映射

resultType 自动映射

在 select 元素中,resultType 属性用于指定查询结果映射的类型。resultType 属性自动映射功能允许将数据库查询结果自动映射到指定的 Java 对象类型。

当设置了 resultType 属性后,MyBatis 会根据数据库表的字段名和 Java 对象的属性名进行自动匹配映射,如果字段名和属性名一致,或者可以通过一定的命名规则进行匹配,MyBatis 会自动将数据库中的数据填充到对应的 Java 对象属性中。


示例:联表查询 resultType 自动映射

用户表实体类(cn.duozai.entity.SysUser):

public class SysUser implements Serializable {

    // ...

    /**
     * 用户角色id
     */
    private Integer roleId;

    /**
     * 用户角色名称
     */
    private String userRoleName;

    // ...

}

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...

    /**
     * 查询用户列表,包含用户角色名称
     *
     * @return 用户列表
     */
    List<SysUser> getUserListWithRoleName();

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <!--
        数据库字段名与实体类属性名一致时,resultType会自动进行结果集转换,进行自动映射
    -->
    <select id="getUserListWithRoleName" resultType="cn.duozai.entity.SysUser">
        SELECT u.*, r.roleName FROM t_sys_user AS u, t_sys_role AS r
        WHERE u.roleId = r.id
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 查询用户列表,包含用户角色名称
     *
     * @return void
     */
    @Test
    public void getUserListWithRoleName() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListWithRoleName();

        for (SysUser sysUser : sysUserList) {
            // realName能自动映射,实体类属性名与数据库字段名一致
            // userRoleName不能自动映射,实体类属性名(userRoleName)与数据库字段名(roleName)不一致
            logger.debug("SysUser => " + sysUser.getRealName() + "的角色名称为:" + sysUser.getUserRoleName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


resultMap 手动映射

在 MyBatis 中,可以使用 resultMap 元素定义结果映射,对名称不同的结果集字段和实体类属性进行手动映射关系的绑定。

resultMap 元素的基本用法:

<resultMap id="映射规则名称" type="映射的结果类型">
    <!-- 映射主键id -->
    <id column="数据库字段名" property="实体类属性名" />
    <!-- 映射其他非主键字段 -->
    <result column="数据库字段名" property="实体类属性名" />
</resultMap>

<select id="..." resultMap="映射规则名称">
    ...
</select>

使用 resultMap 元素实现手动映射的注意事项:

  • select 元素使用 resultMap 属性指定手动映射规则时,不能再使用 resultType 属性。
  • 使用 resultMap 元素进行手动映射,没有指定某些数据库字段和实体类属性的映射关系时,只要数据库字段名和实体类属性名一致,也会进行自动映射。

示例:联表查询 resultMap 手动映射

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <!--
        <resultMap>标签:手动指定字段和属性之间的映射关系规则
        id属性:映射规则的名称
        type属性:将查询结果映射成哪一个Java实体类
    -->
    <resultMap id="userWithRoleName" type="cn.duozai.entity.SysUser">
        <!--
            <id>标签:映射主键字段
            <result>标签:映射普通字段

            property属性:实体类中的属性名
            column属性:数据库中的字段名
        -->
        <id column="id" property="id"/>
        <result column="roleName" property="userRoleName"/>
    </resultMap>

    <!--
        resultMap属性:指定手动映射的规则
        resultMap与resultType只能二选一
    -->
    <select id="getUserListWithRoleName" resultMap="userWithRoleName">
        SELECT u.*, r.roleName FROM t_sys_user AS u, t_sys_role AS r
        WHERE u.roleId = r.id
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 查询用户列表,包含用户角色名称
     *
     * @return void
     */
    @Test
    public void getUserListWithRoleName() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListWithRoleName();

        for (SysUser sysUser : sysUserList) {
            // realName虽然没有手动绑定映射关系,但实体类属性名与数据库字段名一致,它也能自动映射
            // 指定了手动映射规则,实体类属性名(userRoleName)与数据库字段名(roleName)绑定了关系
            logger.debug("SysUser => " + sysUser.getRealName() + "的角色名称为:" + sysUser.getUserRoleName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


联表查询概述

使用联表查询时,结果集中往往会包含与多个实体类相关的信息(在一方包含另一方的引用),此时即可使用嵌套结果映射解决数据封装问题。

联表查询的情况:

  • 一对一:两个表中的记录之间存在一一对应的关系,如一个用户对应一个角色。
  • 一对多:一个实体可以与多个其他实体相关联,如一个用户有多个收货地址。
  • 多对一:多个实体可以与多个其他实体相关联,如一个学生可以选择多门课程,一门课程也可以有多个学生选修。

使用 association 元素实现一对一嵌套结果映射

实现一对一联表查询,可以在两个有一对一关系的实体类里,一方通过属性关联另一方,同时使用 association 元素在 SQL 映射文件中来描述一对一嵌套结果映射的关系,以实现联表查询时数据的正确整合与获取。

association 元素用于处理一对一联表关系映射。

association 元素的基本用法:

<resultMap id="..." type="...">
    <!-- ... -->
    <association property="关联的对象属性名" javaType="关联的对象类型">
        <id column="..." property="..." />
        <result column="..." property="..." />
    </association>
</resultMap>

使用 resultMap 元素 + association 元素实现一对一嵌套结果映射的注意事项:

  • 因为结果映射的需要,要确保查询字段时所有的字段都是唯一的。
  • association 元素可以其结合 resultMap 属性实现映射规则的复用。

示例:联表查询一对一嵌套结果手动映射

角色表实体类(cn.duozai.entity.SysRole):

public class SysRole implements Serializable {

    /**
     * 主键id
     */
    private Integer id;

    /**
     * 角色编码
     */
    private String code;

    /**
     * 角色名称
     */
    private String roleName;

    // ...

}

用户表实体类(cn.duozai.entity.SysUser):

public class SysUser implements Serializable {

    // ...

    /**
     * 用户角色id
     */
    private Integer roleId;

    /**
     * 用户角色名称
     */
    // private String userRoleName;

    /**
     * 关联用户角色表实体类对象
     */
    private SysRole sysRole;

    // ...

}

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 根据用户角色id查询用户列表,包含角色信息
     *
     * @param roleId 用户角色id
     * @return 用户列表
     */
    List<SysUser> getUserListByRoleId(@Param("roleId") Integer roleId);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <resultMap id="userWithRole" type="cn.duozai.entity.SysUser">
        <id property="id" column="id"/>
        <result property="realName" column="realName"/>
        <!--
            在SysUser中嵌套了SysRole,需要把角色信息映射到SysUser中的SysRole
            <association>标签:实现一对一关系映射
            property属性:SysUser中嵌套的SysRole对象的属性名
            javaType属性:SysUser中嵌套的SysRole对象的类型
        -->
        <association property="sysRole" javaType="cn.duozai.entity.SysRole">
            <id property="id" column="rid"/>
            <result property="roleName" column="roleName"/>
            <result property="code" column="code"/>
        </association>
    </resultMap>
    <select id="getUserListByRoleId" resultMap="userWithRole" parameterType="int">
        SELECT u.*, r.id AS rid, r.`code`, r.roleName
        FROM t_sys_user AS u, t_sys_role AS r
        WHERE u.roleId = #{roleId} AND u.roleId = r.id
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据用户角色id查询用户列表,包含角色信息
     *
     * @return void
     */
    @Test
    public void getUserListByRoleId() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        List<SysUser> sysUserList = sqlSession.getMapper(SysUserMapper.class)
                .getUserListByRoleId(2);

        for (SysUser sysUser : sysUserList) {
            logger.debug("SysUser => " + sysUser.getRealName() + "的角色名称为:" + sysUser.getSysRole().getRoleName());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


使用 collection 实现一对多嵌套结果映射

实现一对多联表查询,可以在在一方的实体类里可通过集合属性(如 List、Set 等)来关联多方的多个对象,同时使用 collection 元素在 SQL 映射文件中来描述一对多嵌套结果映射的关系,以实现联表查询时能正确地将数据获取到对应的实体类对象及其关联的集合对象中。

collection 元素用于处理一对多或多对多联表关系映射。

collection 元素的基本用法:

<resultMap id="..." type="...">
    <!-- ... -->
    <collection property="关联的集合属性名" ofType="关联的集合对象中的元素类型">
        <id column="..." property="..." />
        <result column="..." property="..." />
    </collection>
</resultMap>

使用 resultMap 元素 + collection 元素实现一对一嵌套结果映射的注意事项:

  • 因为结果映射的需要,要确保查询字段时所有的字段都是唯一的。
  • collection 元素可以其结合 resultMap 属性实现映射规则的复用。

示例:联表查询一对多嵌套结果手动映射

地址表实体类(cn.duozai.entity.Address):

public class Address implements Serializable {

    /**
     * 主键id
     */
    private Integer id;

    /**
     * 联系人姓名
     */
    private String contact;

    /**
     * 收货地址明细
     */
    private String addressDesc;

    /**
     * 邮编
     */
    private String postCode;

    /**
     * 联系人电话
     */
    private String tel;

    /**
     * 用户id
     */
    private Integer userId;

    // ...

}

用户表实体类(cn.duozai.entity.SysUser):

public class SysUser implements Serializable {

    // ...

    /**
     * 关联用户地址列表
     */
    private List<Address> addressList;

    // ...

}

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 根据用户id查询用户对象,包含地址列表
     *
     * @param id 用户id
     * @return 用户表实体类对象
     */
    SysUser getUserById(@Param("userId") Integer id);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <resultMap id="userWithAddress" type="cn.duozai.entity.SysUser">
        <id property="id" column="id"/>
        <result property="realName" column="realName"/>
        <!--
            <collection>标签:实现一对多关系映射
            property属性:SysUser中嵌套的地址列表集合的属性名
            ofType属性:SysUser中嵌套的地址列表集合中的元素类型
        -->
        <collection property="addressList" ofType="cn.duozai.entity.Address">
            <id property="id" column="aid"/>
            <result property="addressDesc" column="addressDesc"/>
            <result property="tel" column="tel"/>
        </collection>
    </resultMap>
    <select id="getUserById" resultMap="userWithAddress" parameterType="int">
        SELECT u.*, a.id AS aid, a.contact, a.addressDesc, a.postCode, a.tel
        FROM t_sys_user AS u, t_address AS a
        WHERE u.id = a.userId AND u.id = #{userId}
    </select>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据用户id查询用户对象,包含地址列表
     *
     * @return void
     */
    @Test
    public void getUserById() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        SysUser sysUser = sqlSession.getMapper(SysUserMapper.class)
                .getUserById(1);

        logger.debug("SysUser => " + sysUser.getRealName());
        for (Address address : sysUser.getAddressList()) {
            logger.debug("Address => " + address.getAddressDesc());
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


resultType 与 resultMap 总结

在 select 元素中,resultType 属性直接表示返回类型,适用于比较简单直接的数据封装场景。

而 resultMap 属性是对外部 resultMap 的引用,能够处理结果集字段名与实体类属性名不一致、或者需要对连接查询结果使用嵌套映射等较为复杂的问题。

二者本质上都是基于 Map 数据结构,不能同时使用。

自动映射行为

MyBatis 自动映射的情况:

  • 单表查询使用 resultType 时,数据库字段名与实体类属性名一致,可以自动映射。
  • 单表查询使用 resultMap 时,可以实现手动映射关系。resultMap 中未定义的映射关系,只要数据库字段名与实体类属性名一致,也可以自动映射。
  • 多表查询使用 resultMap+association/collection 嵌套映射时,默认不会对嵌套映射的 resultMap 进行自动映射。resultMap 中未定义的映射关系,即使数据库字段名与实体类属性名一致,也不会自动映射。此时可以配置 MyBatis 自动映射级别实现自动映射。

MyBatis 自动映射级别(autoMappingBehavior):

自动映射行为resultTyperesultMapresultMap+association/collection
NONE失效手动映射手动映射
PARTIAL(默认)自动映射自动映射手动映射
FULL自动映射自动映射自动映射

MyBatis 自动映射的前提:数据库字段名与实体类属性名一致。


示例:自动映射级别

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...
  
    /**
     * 根据用户角色id查询用户列表,包含角色信息
     *
     * @param roleId 用户角色id
     * @return 用户列表
     */
    List<SysUser> getUserListByRoleId(@Param("roleId") Integer roleId);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <resultMap id="userWithRole" type="cn.duozai.entity.SysUser">
        <id property="id" column="id"/>
        <!-- 不映射realName,查不到数据 -->
        <!-- <result property="realName" column="realName"/> -->
        <association property="sysRole" javaType="cn.duozai.entity.SysRole">
            <id property="id" column="rid"/>
            <result property="roleName" column="roleName"/>
            <result property="code" column="code"/>
        </association>
    </resultMap>
    <select id="getUserListByRoleId" resultMap="userWithRole" parameterType="int">
        SELECT u.*, r.id AS rid, r.`code`, r.roleName
        FROM t_sys_user AS u, t_sys_role AS r
        WHERE u.roleId = #{roleId} AND u.roleId = r.id
    </select>

    <!-- ... -->
</mapper>

示例效果:

MyBatis 核心配置文件(classpath:mybatis-config.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- ... -->

    <settings>
        <!-- ... -->
        <!-- 配置自动映射级别:即使没有进行手动映射,只要字段名与属性名一致,就能自动映射 -->
        <setting name="autoMappingBehavior" value="FULL"/>
    </settings>

    <!-- ... -->
</configuration>

示例效果:


自动映射的特殊情况

一般情况下,数据库字段名以下划线命名(xx_xx),而实体类中的属性名以驼峰命名(xxXx),此时数据库字段名与实体类中的属性名不一致。

在 MyBatis 中,可以配置驼峰命名自动转换(mapUnderscoreToCamelCase),以实现自动映射。


示例:自动映射的特殊情况

MyBatis 核心配置文件(classpath:mybatis-config.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- ... -->

    <settings>
        <!-- ... -->
        <!-- 配置驼峰自动转换:将字段xx_yy自动映射到属性xxYy -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!-- ... -->
</configuration>

MyBatis 增删改操作

增删改操作概述

在使用 MyBatis 增删改元素执行 SQL 语句时,需要注意:

  • 对于增删改操作,数据库本身默认返回执行 SQL 语句所影响的行数,因此 Mapper 接口的返回值一般设置为 int 类型。
  • 映射增删改 SQL 语句的元素没有 resultType/resultMap 属性。
  • 执行增删改操作时,需要注意数据库事务控制。
  • 一般情况下,参数数量较多时最好封装成对象入参,特别是常规的添加和修改操作,字段较多,封装对象比较方便。
  • 参数量较少时,可以直接多参数入参。
  • 无论是多参数入参还是单参数入参,都建议使用 @Param 注解对参数合理命名。

执行 insert 语句

在 MyBatis 中,insert 元素用于定义新增语句。


示例:执行 insert 语句

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...

    /**
     * 添加用户
     *
     * @param sysUser 用户表实体类对象
     * @return SQL语句影响行数
     */
    int add(SysUser sysUser);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <insert id="add" parameterType="cn.duozai.entity.SysUser">
        INSERT INTO t_sys_user(account, realName, password, sex, birthday, phone, address, roleId)
        VALUES(#{account}, #{realName}, #{password}, #{sex}, #{birthday}, #{phone}, #{address}, #{roleId})
    </insert>
</mapper>

MyBatis 工具(cn.duozai.utils.MyBatisUtil):

public class MyBatisUtil {

    // ...

    /**
     * 创建并返回SqlSession实例
     *
     * @return SqlSession实例
     */
    public static SqlSession createSqlSession() {
        // 关闭自动提交,开启事务控制
        return sqlSessionFactory.openSession(false);
    }

    // ...

}

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 添加用户
     *
     * @return void
     */
    @Test
    public void add() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        SysUser sysUser = new SysUser();
        sysUser.setAccount("addtest1");
        sysUser.setRealName("添加测试");

        int result = sqlSession.getMapper(SysUserMapper.class).add(sysUser);

        if(result == 1) {
            logger.debug("用户" + sysUser.getRealName() + "添加成功");
        } else {
            logger.debug("用户" + sysUser.getRealName() + "添加失败");
        }

        // sqlSessionFactory.openSession(false) 时需要手动提交事务
        // 将openSession的参数设置为true,则不需要手动提交事务,其自动提交事务
        sqlSession.commit();

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


执行 update 语句

在 MyBatis 中,update 元素用于定义修改语句。


示例:执行 update 语句

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...

    /**
     * 根据用户id修改密码
     *
     * @param id 用户id
     * @param newPassword 新密码
     * @return SQL语句影响行数
     */
    int updatePassword(@Param("userId") Integer id, @Param("newPassword") String newPassword);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <update id="updatePassword">
        UPDATE t_sys_user SET password = #{newPassword}
        WHERE id = #{userId}
    </update>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据用户id修改密码
     *
     * @return void
     */
    @Test
    public void updatePassword() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        int result = sqlSession.getMapper(SysUserMapper.class)
                .updatePassword(12, "888999");

        if(result == 1) {
            logger.debug("用户密码修改成功");
        } else {
            logger.debug("用户密码修改失败");
        }

        // MyBatisUtil已开启自动提交事务
        // sqlSessionFactory.openSession(true);
        // sqlSession.commit();

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


执行 delete 语句

在 MyBatis 中,delete 元素用于定义删除语句。


示例:执行 delete 语句

用户表数据访问接口(cn.duozai.dao.SysUserMapper):

public interface SysUserMapper {

    // ...

    /**
     * 根据用户id删除用户
     *
     * @param id 用户id
     * @return SQL语句影响行数
     */
    int deleteUserById(@Param("userId") Integer id);

}

用户表数据访问接口 SQL 映射文件(cn/duozai/dao/SysUserMapper.xml):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.duozai.dao.SysUserMapper">
    <!-- ... -->

    <delete id="deleteUserById">
        DELETE FROM t_sys_user WHERE id = #{userId}
    </delete>
</mapper>

测试类(test.SysUserMapperTest):

public class SysUserMapperTest {

    // ...

    /**
     * 根据用户id删除用户
     *
     * @return void
     */
    @Test
    public void deleteUserById() {
        SqlSession sqlSession = MyBatisUtil.createSqlSession();

        int result = sqlSession.getMapper(SysUserMapper.class)
                .deleteUserById(12);

        if(result == 1) {
            logger.debug("用户删除成功");
        } else {
            logger.debug("用户删除失败");
        }

        MyBatisUtil.closeSqlSession(sqlSession);
    }

}

示例效果:


MyBatis 缓存

MyBatis 缓存概述

MyBatis 的缓存分为两个级别:一级缓存和二级缓存。

MyBatis 一级缓存:

  • 一级缓存是基于 PerpetualCache 的 HashMap 本地缓存,默认是 SqlSession 级别的缓存。
  • 一级缓存在 SqlSession 的一个生命周期内有效,当 SqlSession 关闭后,该 SqlSession 中的所有一级缓存会被清空。
  • 一级缓存默认是开启的。

MyBatis 二级缓存:

  • 二级缓存是 SqlSessionFactory 级别的缓存,缓存中的数据可以被所有的 SqlSession 共享。
  • 二级缓存默认是关闭的,使用 MyBatis 二级缓存,需要在 MyBatis 核心配置中全局开启二级缓存(cacheEnabled),并在 SQL 映射文件中配置缓存。

MyBatis 缓存在数据量达到一定规模时,其无法满足要求。

MyBatis 的核心是 SQL 管理,缓存并不是 MyBatis 所擅长的,实现缓存机制可以采用 Redis、MongoDB、Memcached、OSCache 等专业缓存服务器。