这是基于b站狂神的JavaWeb课程的笔记 课程链接
笔记第一部分链接:MyBatis 入门笔记+理解(一)
9. 复杂查询之多对一
9.1 环境搭建
- sql建表
CREATE TABLE `teacher` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, 秦老师);
CREATE TABLE `student` (
`id` INT(10) NOT NULL,
`name` VARCHAR(30) DEFAULT NULL,
`tid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fktid` (`tid`),
CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (1, 小明, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (2, 小红, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (3, 小张, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (4, 小李, 1);
INSERT INTO `student` (`id`, `name`, `tid`) VALUES (5, 小王, 1);
- 构造实体类
学生
@Data
public class Student {
private int id;
private String name;
// 学生需要关联一个老师
private Teacher teacher;
}
老师
@Data
public class Teacher {
private int id;
private String name;
}
9.2 多对一
- 现有需求:获取所有学生的信息和他们关联的老师的信息
多名学生和一个老师关联
需求对应的sql语句:
select s.id,s.name,t.id,t.name
from student s, teacher t
where s.tid = t.id;
- 思路:
- 查询出所有学生的信息
- 根据查出来学生的tid,查询出对应关联的老师的信息
- 根据思路编写Mapper.xml:
方法一:根据查询嵌套处理
<!-- 根据查询嵌套处理 -->
<!-- 1. 查询所有学生信息 -->
<select id="getStudents" resultMap="studentAndTeacher">
select * from student
</select>
<!-- 两次查询用一个resultMap关联起来 -->
<resultMap id="studentAndTeacher" type="student">
<id property="id" column="id"/>
<result property="name" column="name"/>
<!--
复杂的属性,单独处理:对象用association,集合用collection
属性是一个对象,所以先用javaType来规定是哪个类
而从表中获得的是一个tid,所以需要用一个字查询语句,用tid获取整个Teacher对象
select="getTeachers"就规定了用如下的这个sql去获得teacher的信息
-->
<association property="teacher" column="tid" javaType="Teacher" select="getTeachers"/>
</resultMap>
<!-- 2. 根据学生查出来的tid,查询对应的老师! (子查询) -->
<select id="getTeachers" resultType="teacher">
select * from teacher where id=#{tid}
</select>
- 本质上,第一个
<select>
中select * from student
得到的表字段为tid
而学生类的属性为teacher
,所以需要一个结果集映射将他们对应起来 - 而恰好
teacher
是一个对象 (用javaType="Teacher"
表示),不能简单地只将名称进行映射,还需要得到teacher
的内部属性信息,所以我们又需要一个子查询,就是第二个<select>
中的sql查询。
方法二:根据结果嵌套处理
<!-- 根据结果嵌套处理 -->
<select id="getStudents2" resultMap="mapTeacher">
select s.id sid, s.name sname, t.id tid, t.name tname
from student s, teacher t
where s.tid = t.id
</select>
<resultMap id="mapTeacher" type="Student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
<association property="teacher" javaType="Teacher">
<!--
根据查询到的结果(tid, tname),
来与Teacher这个类中的属性进行映射
-->
<result property="id" column="tid"/>
<result property="name" column="tname"/>
</association>
</resultMap>
10. 复杂查询之一对多
与9中的情况相对应,一个老师拥有多个学生,此时称为一对多
11.1 环境搭建
- 表与9中相同
- 实体类
老师
@Data
public class Teacher {
private int id;
private String name;
// 一个老师拥有多个学生
private List<Student> students;
}
学生
@Data
public class Student {
private int id;
private String name;
private int tid;
}
10.2 一对多处理
- 需求:根据id查询老师信息和这个老师拥有的所有学生信息
- sql:
select t.id tid, t.name tname, s.id sid, s.name sname
from student s, teacher t
where t.id = s.tid and t.id = #{tid}
- 接口:
public interface TeacherMapper {
// 根据id查询老师信息和这个老师拥有的所有学生信息
// 使用@param("tid")后,则Mapper.xml中的sql语句中应用对应的#{tid}
// 提取该参数
Teacher getTeacher(@Param("tid") int id);
}
方法一:根据结果嵌套处理
<!-- 根据结果嵌套处理 -->
<select id="getTeacher" resultMap="tsMap">
select t.id tid, t.name tname, s.id sid, s.name sname
from student s, teacher t
where t.id = s.tid and t.id = #{tid}
</select>
<resultMap id="tsMap" type="teacher">
<!-- 名称映射 -->
<result property="id" column="tid"/>
<result property="name" column="tname"/>
<!--
属性为集合时 ==> collection
集合中的泛型用 ofType 规定
-->
<collection property="students" ofType="student">
<result property="id" column="sid"/>
<result property="name" column="sname"/>
</collection>
</resultMap>
- 先拿到结果
t.id tid, t.name tname, s.id sid, s.name sname
- 再用拿到的这些结果与老师的属性,以及老师拥有的学生的属性作一一映射
方法二:根据查询嵌套处理
<!-- 根据查询嵌套处理 -->
<!-- 先根据id拿到老师 -->
<select id="getTeacher2" resultMap="tsMap2">
select * from teacher
where id = #{tid}
</select>
<resultMap id="tsMap2" type="teacher">
<!--
注意:这里即使名称相同,也要显式地映射一次
因为column中的id在下面被映射给了"students"
这里不映射会导致查不到teacher的id
-->
<result property="id" column="id"/>
<!--
属性名称为students
类为Arraylist,集合中的泛型为student
再用一个子查询去查tid为"id"的学生
-->
<collection property="students" javaType="Arraylist" ofType="student" column="id" select="getStudent"/>
</resultMap>
<!-- 再根据老师的id拿到tid为老师的id的学生 -->
<select id="getStudent" resultType="student">
select * from student
where tid = #{id}
</select>
11. 动态SQL
- 减少了根据不同条件拼接 SQL 语句的痛苦
11.1 If
一个官方文档中的例子足矣:
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
一般将一个 Map
作为参数,若在 Map
中放入了 title
的值,则可以被取出并放到 <if>
中的 AND
语句里
11.2 Choose
与Java中的 switch
类似:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
根据 <when>
的编写顺序查看是否满足条件,只会执行第一条满足条件的 <when>
,若没有满足条件的则执行 <otherwise>
11.3 Where, Set, Trim
Where
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
官方doc一句话:
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
也就是说在上面这个例子中,若没有一个 if
被满足,where将自动消失,若只有第二个 if
或 只有第三个 if
被满足,他们前面的 AND
将自动消失。
Set
<update id="updateAuthorIfNecessary">
update Author
<set>
<if test="username != null">username=#{username},</if>
<if test="password != null">password=#{password},</if>
<if test="email != null">email=#{email},</if>
<if test="bio != null">bio=#{bio}</if>
</set>
where id=#{id}
</update>
这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
trim
可自定义 where
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
也可自定义 set
<trim prefix="SET" suffixOverrides=",">
...
</trim>
11.4 foreach
需求的SQL:
SELECT * FROM user
WHERE gender = 'male'
AND ( id = 1 OR id = 2 OR id = 4 )
- 在接口的参数中使用
Map<String, Object>() map
- 在map中添加一个list
ArrayList<Integer> ids = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
map.put("ids", ids)
在mapper.xml中编写
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM user
WHERE Gender = #{male}
<foreach item="id" collection="ids"
open=" AND (" separator="OR" close=")">
#{id}
</foreach>
</select>
12. 缓存
12.1 一级缓存
- 默认开启
-
Sqlsession
级别的缓存 - 通俗地说,一级缓存只存在于其对应的一个
Sqlsession
的生命周期中:
// 对于这个sqlSession的缓存,只存在于这两行代码之间
SqlSession sqlSession = MyBatisUtils.getSqlSession();
...
sqlSession.close()
- 在一个
sqlSession
中,查询同一个东西,只会在第一次查询那个东西时连接到数据库,之后都会直接从缓存中拿去 - 有任何增、删、改的操作都会清除缓存
-
sqlSession.clearCache()
也可手动清除缓存 - 一级缓存就是一个Map
12.2 二级缓存
- 基于
namespace
级别的缓存 - 需手动开启
<!-- 显式地开启全局缓存> <setting name="cacheEnabled" value="true"/>
也可以自定义参数<!-- 在当前Mapper.xml中使用二级缓存 --> <cache/>
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
- 开启后,当一个会话结束了,其对应的缓存在消失之前会进入到二级缓存,也就是当前Mapper的缓存中
12.3 查看缓存的顺序
在一次会话中做一次查询
- 先查看当前Mapper中的二级缓存中有没有查询结果,若没有
- 再查看当前会话对应的一级缓存中有没有,若没有
- 查询数据库