MyBatis:使用 MyBatis 实现多表查询(多对一和一对多)、动态 SQL、缓存

MyBatis:Day 03

一、复杂查询的环境搭建

复杂查询的环境搭建:为接下来的多对一和一对多的处理做准备。

总体步骤为:

  1. pom.xml 中导入所需要的依赖;
  2. 建立 MyBatis 核心配置文件:mybatis-config.xml
  3. 工具类:MybatisUtils.java
  4. 实体类:Teacher.javaStudent.java
  5. 接口:TeacherMapper.javaStudentMapper.java
  6. 实现类:TeacherMapper.xmlStudentMapper.xml
  7. 测试。

需要提前建立两张表:studentteacher 表。

-- 创建数据库 mybatis01
CREATE DATABASE IF NOT EXISTS `mybatis01`;
USE `mybatis01`;

-- 创建 teacher 表
CREATE TABLE IF NOT EXISTS `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

-- 插入数据
INSERT INTO `teacher` (`id`, `name`) VALUES (1, '张老师'), (2, '王老师'); 

-- 创建 student 表
CREATE TABLE IF NOT EXISTS `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

-- 插入数据
INSERT INTO `student` (`id`, `name`, `tid`) VALUES 
(1, '小明', 1), 
(2, '小红', 1), 
(3, '小张', 2), 
(4, '小李', 2), 
(5, '小王', 2);

环境搭建显示

注意:这里把实现类 Mapper.xml 放入了资源文件夹下,但经过编译后仍在 dao 包下,所以在核心配置文件中的资源路径为:resource="com/Sun3285/dao/StudentMapper.xml"

生成的 target 文件夹


二、多表查询

最佳实践:最好逐步建立结果映射。单元测试可以在这个过程中起到很大帮助。 如果你尝试一次性创建复杂的结果映射,不仅容易出错,难度也会直线上升。 所以,从最简单的形态开始,逐步迭代。而且别忘了单元测试! 有时候,框架的行为像是一个黑盒子(无论是否开源)。因此,为了确保实现的行为与你的期望相一致,最好编写单元测试。 并且单元测试在提交 bug 时也能起到很大的作用。

1. 多对一:关联

举例:查询全部学生(多)对应的老师(少)。

(1)联表查询

思路

  1. 联表查询是按照结果嵌套处理;
  2. 步骤:先写出 SQL 语句,保证可以执行,然后在 Mapper.xml 中用 resultMap 进行结果集映射,最后测试。
  • SQL 语句:
SELECT s.`id` AS '学生id', s.`name` AS '学生姓名', s.`tid` AS '老师id', t.`name` AS '老师姓名' 
FROM `student` AS s 
INNER JOIN `teacher` AS t 
ON s.`tid` = t.`id`;

  • Mapper.xml 中用 resultMap 进行结果集映射:
<select id="getAllStudents" resultMap="studentResultMap">
    SELECT s.`id` AS '学生id', s.`name` AS '学生姓名', s.`tid` AS '老师id', t.`name` AS '老师姓名'
    FROM `student` AS s
    INNER JOIN `teacher` AS t
    ON s.`tid` = t.`id`;
</select>

<resultMap id="studentResultMap" type="Student">
    <result property="id" column="学生id"/>
    <result property="name" column="学生姓名"/>
    <!-- 复杂类型是对象,用 association -->
    <association property="teacher" javaType="Teacher">
        <result property="id" column="老师id"/>
        <result property="name" column="老师姓名"/>
    </association>
</resultMap>

  • 测试:

(2)子查询

思路:子查询是按照查询过程嵌套处理;

<select id="getAllStudents" resultMap="studentResultMap">
    select * from `student`;
</select>

<resultMap id="studentResultMap" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="Teacher">
    select * from teacher where `id` = #{tid};
</select>

2. 一对多:集合

举例:查询全部老师(少)包含的学生(多)。

(1)联表查询

思路

  1. 联表查询是按照结果嵌套处理;
  2. 步骤:先写出 SQL 语句,保证可以执行,然后在 Mapper.xml 中用 resultMap 进行结果集映射,最后测试。
  • SQL 语句:
SELECT t.`id` AS '老师id', t.`name` AS '老师姓名', s.`id` AS '学生id', s.`name` AS '学生姓名' 
FROM `teacher` AS t 
INNER JOIN `student` AS s 
ON t.`id` = s.`tid`;

  • Mapper.xml 中用 resultMap 进行结果集映射:
<select id="getAllTeachers" resultMap="teacherResultMap">
    SELECT t.`id` AS '老师id', t.`name` AS '老师姓名', s.`id` AS '学生id', s.`name` AS '学生姓名'
    FROM `teacher` AS t
    INNER JOIN `student` AS s
    ON t.`id` = s.`tid`;
</select>

<resultMap id="teacherResultMap" type="Teacher">
    <result property="id" column="老师id"/>
    <result property="name" column="老师姓名"/>
    <collection property="studentList" ofType="Student">
        <result property="id" column="学生id"/>
        <result property="name" column="学生姓名"/>
    </collection>
</resultMap>

  • 测试:

(2)子查询

This part is too complex, let’s use linked table queries instead!

3. 总结

  1. 当查询出来的字段名属性名不一致时,需要进行结果集映射 resultMap

  2. 使用结果集映射 resultMap 时,属性名和查询到的字段名要一一映射

    • 简单的属性(只是属性名和字段名不一致),直接映射;
    • 复杂的属性(如自定义对象、集合)需要单独处理(嵌套处理):
      • 属性是自定义对象:用 association,其中 javaType 为属性中自定义的类
      • 属性是集合:用 collection,其中 ofType 为属性中集合的泛型约束类型
  3. 保证 SQL 的可读性,通俗易懂。

  4. 排查错误可以使用日志


三、动态 SQL 的环境搭建

动态 SQL 的环境搭建:为接下来的动态 SQL 学习做准备。

总体步骤为:

  1. pom.xml 中导入所需要的依赖;
  2. 建立 MyBatis 核心配置文件:mybatis-config.xml
  3. 工具类:MybatisUtils.java 以及 IDUtils.java(用来产生一个唯一的 ID);
  4. 实体类:Blog.java
  5. 接口:BlogMapper.java
  6. 实现类:BlogMapper.xml
  7. 测试。

需要提前建立一张表:blog 表。

-- 创建数据库 mybatis02
CREATE DATABASE IF NOT EXISTS `mybatis02`;

USE `mybatis02`;

-- 创建表 blog
CREATE TABLE IF NOT EXISTS `blog`(
`id` VARCHAR(50) NOT NULL COMMENT '博客id',
`title` VARCHAR(100) NOT NULL COMMENT '博客标题',
`author` VARCHAR(30) NOT NULL COMMENT '博客作者',
`create_time` DATETIME NOT NULL COMMENT '创建时间',
`views` INT(30) NOT NULL COMMENT '浏览量'
)ENGINE=INNODB DEFAULT CHARSET=utf8mb4;

测试:插入数据。

环境搭建过程


四、动态 SQL

动态 SQL:根据不同的条件生成不同的 SQL 语句,在 SQL 层面执行一些逻辑代码

实质拼接 SQL 语句,只要保证 SQL 的正确性,按照 SQL 的格式去排列组合就可以。

建议:先写出完整的 SQL,然后再对应地去修改成为动态 SQL。

1. if

<!-- 在实现类 Mapper.xml 中配置 -->
<select id="方法名" parameterType="参数类型" resultType="返回值类型">
    select 查询语句
    <where>
        <if test="判断语句1(结果为布尔值类型)">
            sql 语句
        </if>
        <if test="判断语句2(结果为布尔值类型)">
            sql 语句
        </if>
    </where>
</select>

2. choose、when、otherwise

如果有多个 IF 语句,会从上到下依次对每个条件进行判断。有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<!-- 在实现类 Mapper.xml 中配置 -->
<select id="方法名" parameterType="参数类型" resultType="返回值类型">
    select 查询语句
    <where>
        <choose>
            <when test="判断语句1(结果为布尔值类型)">
                sql 语句
            </when>
            <when test="判断语句2(结果为布尔值类型)">
                sql 语句
            </when>
            <otherwise>
                sql 语句
            </otherwise>
        </choose>
    </where>
</select>

注意:choose 元素,类似于 Java 中的 switch 语句:

  • choose 等同于 switch;
  • when 等同于 case;
  • otherwise 等同于 default。

3. where、set

where 元素作用

  • 至少有一个子元素的条件返回 SQL 子句的情况下才插入 “WHERE” 子句;
  • 如果子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除

set 元素作用

  • 会动态地在行首插入 SET 关键字;
  • 删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

4. foreach

foreach:用来对集合进行遍历(尤其是在构建 IN 条件语句的时候)。

可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。

当使用可迭代对象或者数组时:

  • collection :指传递的集合参数,如果 parameterType 为 Map 集合,这里就是指键名;

  • item :每次迭代获取到的元素;

  • open :以 xxx 为开始;

  • separator :分隔;

  • close :以 xxx 为结束。

<!-- 在实现类 Mapper.xml 中配置 -->
<select id="方法名" parameterType="map" resultType="返回值类型">
    select 查询语句
    <where>
        <foreach collection="传递的集合参数" item="每次迭代获取到的元素"
                 open="开始" separator="分隔" close="结束">
            `id` = #{id}
        </foreach>
    </where>
</select>

举例:查询指定 ID 的博客信息。

5. SQL 片段

有的时候,我们可能会将一些公共部分抽取出来,方便复用,如下所示:

<!-- SQL 片段 -->
<sql id="SQL 片段的 id(自由取)">
	公共的 SQL 片段
</sql>

引用 SQL 片段时,如下所示:

<!-- 使用 SQL 片段 -->
<select id="方法名" parameterType="map" resultType="返回值类型">
    select 查询语句
    <where>
        <include refid="SQL 片段的 id"></include>
    </where>
</select>

注意

  1. 最好基于单表来定义 SQL 片段;
  2. SQL 片段中不要存在 where 标签。

6. 总结

  1. 多个 IF 语句会从上到下依次对每个条件进行判断;

  2. choose 元素,像 Java 中的 switch 语句,从上到下执行,只要有一个符合就停止执行;

  3. foreach 元素是遍历循环,集合参数为可迭代对象(如 List、Set 等)、Map 对象或者数组对象;

  4. 最好将 SQL 中的 where 用 where 标签代替;

  5. 判断 test 时,里面的语句为 Java 语句,其中对字符串的判断,要用单引号套双引号的形式,因为 MyBatis 是使用 OGNL 表达式来进行解析的。

<where>
    <if test='author == "Sun3285"'>
    	SQL 语句
    </if>
</where>

五、缓存

1. 简介

缓存:Cache,存在内存中的临时数据

  • 作用:将用户经常查询的数据放在缓存中,用户去查询数据就不用从磁盘上查询,而是从缓存中查询;
  • 优点减少了和数据库的交互次数,提高了查询效率,解决了高并发系统的性能问题;
  • 使用缓存的数据经常查询并且不经常改变的数据。

MyBatis 缓存:包含了一个非常强大的缓存特性,可以非常方便地定制和配置缓存。默认定义了两级缓存

  • 一级缓存:也叫本地缓存,是 sqlSession 级别的缓存。默认情况下,只有一级缓存开启,仅仅对一个会话中的数据进行缓存。
  • 二级缓存:是 namespace 级别的缓存。需要手动开启和配置,MyBatis 定义了缓存接口 Cache ,可以实现接口来定义二级缓存。

注意:二级缓存作用于 cache 标签所在的映射文件中的语句。

2. 一级缓存

  • 一级缓存是 sqlSession 级别的缓存,默认情况下,只有一级缓存开启;

  • 仅仅对一个会话中的数据进行缓存(sqlSession 从得到到关闭这一段期间的数据)。

测试一级缓存。

缓存失效的情况:

  1. 查询不同的数据;
  2. 进行了增、删、改操作,必定会刷新缓存,因为可能改变了原来的数据;
  3. 查询不同的实现类 Mapper.xml
  4. 手动清理缓存sqlSession.clearCache();

注意:一级缓存相当于一个 Map 集合。

3. 二级缓存

二级缓存是 namespace 级别的缓存,会在整个 Mapper.xml 中生效。需要手动开启和配置

工作机制:当关闭会话时,一级缓存中的数据会被保存到二级缓存中,新的会话查询信息时,就可以从二级缓存中获取内容。

开启二级缓存步骤

  1. 在 MyBatis 核心配置文件中进行设置(settings),开启全局缓存
  2. 在要使用二级缓存的 Mapper.xml 中加 cache 标签。

测试二级缓存。

注意实体类需要序列化。

4. 缓存原理

在缓存中查询的顺序

  1. 先看二级缓存中有没有之前查过的数据;
  2. 再看一级缓存中有没有之前查过的数据;
  3. 如果都没有,则查询数据库。

举例

查询一个数据 --> 二级缓存中没有 --> 一级缓存中没有 --> 连接数据库,查询数据库 --> 查询到结果,结果保存到一级缓存中 --> 关闭会话,此时一级缓存中的数据保存到二级缓存中;

再次查询同一个数据 --> 二级缓存中存在 --> 从二级缓存中直接拿到数据(不会连接数据库)。


注意:

  1. Java 代码编译为 class 文件,然后对 class 文件进行执行。
  2. 编译前,代码按照功能分类;编译后,代码按照包名分类。
  3. xml 文件中路径和全限名依据编译后 target 文件中的路径和全限名。
  4. 一定要注意 resultTyperesultMap 别写错。
  5. 实体类最好序列化:实体类实现 Serializable 接口。
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sun 3285

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值