一、多表联合查询
1.1 多表联合查询概述
在开发过程中单表查询不能满足项目需求分析功能,对于复杂业务来讲,关联的表有几张,甚至几十张并且表与表之间的关系相当复杂。为了能够实现复杂功能业务,就必须进行多表查询,在mybatis中提供了多表查询的结果时映射标签,可以实现表之间的一对一、一对多、多对一、多对多关系映射。
二、MyBatis多表查询之一对一
2.1 表准备
使用之前导入的用户表和订单表:

2.2 准备项目环境
2.2.1 构建maven项目,添加依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.sy.mapper</groupId>
<artifactId>day05_mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!--导入mysql依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<!--导入junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>test</scope>
</dependency>
<!--log4j打印日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>
</project>
2.2.2 项目中加入相关配置文件
(db.properties,log4j.properties,SqlMapConfig.xml和MybatisUtils工具类文件等)

2.2.3 创建orders和user表对应的实体类
package com.sy.pojo;
import java.awt.*;
import java.util.List;
public class User {
private int id; //编号id
private String username; //用户名
private String password; //密码
private int age; //年龄
private String sex; //性别
private String email; //邮箱
private List<Order> orderList;
public List<Order> getOrderList() {
return orderList;
}
public void setOrderList(List<Order> orderList) {
this.orderList = orderList;
}
public User() {
}
public User(String username, String password, int age, String sex, String email) {
this.username = username;
this.password = password;
this.age = age;
this.sex = sex;
this.email = email;
}
public User(int id, String username, String password, int age, String sex, String email) {
this.id = id;
this.username = username;
this.password = password;
this.age = age;
this.sex = sex;
this.email = email;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", sex='" + sex + '\'' +
", email='" + email + '\'' +
", orderList=" + orderList +
'}';
}
}
package com.sy.pojo;
public class Order {
// 订单id
private Integer id;
// 用户id,该属性名和数据库的字段名不一致
private Integer userId;
// 订单号
private String number;
// 订单创建时间
private String createTime;
// 备注
private String note;
private User user;
public Order() {
}
public Order(Integer id, Integer userId, String number, String createTime, String note) {
this.id = id;
this.userId = userId;
this.number = number;
this.createTime = createTime;
this.note = note;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getCreateTime() {
return createTime;
}
public void setCreateTime(String createTime) {
this.createTime = createTime;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", userId=" + userId +
", number='" + number + '\'' +
", createTime='" + createTime + '\'' +
", note='" + note + '\'' +
", user=" + user +
'}';
}
}
2.3 需求:查询所有订单信息,关联查询下单用户信息
注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。
2.4 方法一: 嵌套结果方式
介绍:
嵌套结果 是使用嵌套结果映射来处理重复的联合结果的子集。
特点:
只有一条复杂的 SQL 语句(多表连接)
方法:
使用resultMap,定义专门的resultMap用于映射一对一查询结果。
2.4.1 改造pojo
在Order类中加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。
改造Order如下图:

最后再重新生成toString方法即可
2.4.2 Mapper接口
编写OrderMapper接口, 创建查询订单和订单对应用户的方法:queryOrderUserResultMap

2.4.3 OrderMapper映射文件
<?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">
<!-- namespace:命名空间 -->
<mapper namespace="com.sy.mapper.OrderMapper">
<!--查询所有订单信息,关联查询下单用户信息 方式一:嵌套结果方式-->
<select id="findOrderUserResultMap" resultMap="orderUserResultMap">
SELECT
o.id oid,
o.user_id,
o.number,
o.create_time,
o.note,
u.id uid,
u.username,
u.password,
u.age,
u.sex,
u.email
FROM
`orders` o,
`t_user` u
WHERE
o.user_id = u.id
</select>
<!--使用resultMap设置结果集和实体类中属性的映射关系-->
<resultMap id="orderUserResultMap" type="order">
<!--两部分数据: 订单 + 用户信息-->
<id column="oid" property="id"/>
<result column="user_id" property="userId"/>
<result column="number" property="number"/>
<result column="create_time" property="createTime"/>
<result column="note" property="note"/>
<!--设置数据之间的一对一映射关系使用association-->
<!--
property="user": 一对一关联后封装的数据所对应的属性名, 代表的是Order对象中的user属性
javaType="com.sy.pojo.User": 指定user是属于什么具体类型
<id column="user_id" property="id"></id> 写id和user_id是一样的效果, 因为两者等价
-->
<association property="user" javaType="com.sy.pojo.User">
<id column="user_id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<result column="email" property="email"/>
</association>
</resultMap>
<mapper>
2.4.4 测试程序

2.5 方法二: 嵌套查询方式
介绍:
嵌套查询 是指通过执行另外一条 SQL 映射语句来返回预期的复杂类型;
特点:
嵌套查询是在查询 SQL 后再进行一个(子)查询、会执行多条 SQL 语句
方法:
先查询全部的订单数据,再查询每个订单数据对应的用户;
2.5.1 创建UserMapper接口

2.5.2 创建UserMapper.xml映射文件

2.5.3 创建OrderMapper接口

2.5.4 创建OrderMapper.xml映射文件

2.5.5 测试程序
三、MyBatis多表查询之一对多
3.1 案例需求
案例:查询所有用户信息及用户关联的订单信息。
用户信息和订单信息为一对多关系。
3.2 方式一: 嵌套结果方式
3.2.1 修改pojo类

3.2.2 创建UserMapper接口

3.2.3 创建UserMapper.xml映射文件
<!--查询所有用户信息, 关联查询出订单信息 方法一:嵌套结果方式-->
<select id="findUserOrders" resultMap="userResultMap">
SELECT
u.id uid,
u.username,
u.password,
u.age,
u.sex,
u.email,
o.id,
o.user_id,
o.number,
o.create_time,
o.note
FROM
t_user u,
orders o
WHERE
u.id = o.user_id
</select>
<resultMap id="userResultMap" type="user">
<id column="uid" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<result column="email" property="email"/>
<!--
collection: 设置一对多的映射关系
property: 封装pojo实体类中订单属性的属性名
javaType="list": orderList集合的类型
ofType="User": orderList集合的泛型的类型
-->
<collection property="orderList" javaType="list" ofType="order">
<id column="id" property="id"></id>
<result column="user_id" property="userId"></result>
<result column="number" property="number"></result>
<result column="create_time" property="createTime"></result>
<result column="note" property="note"></result>
</collection>
</resultMap>
3.2.4 测试程序

3.3 方式二: 嵌套查询方式
先查询所有的用户,再根据用户的id查询对应的订单
3.3.1 创建OrderMapper接口
在OrderMapper接口中定义根据用户id查询订单的方法:

3.3.2 创建OrderMapper.xml文件

3.3.3 创建UserMapper接口
接口中加入查询所有用户的方法,并查询用户对应的订单:

3.3.4 创建UserMapper.xml映射文件

3.3.5 测试程序

四、MyBatis多表查询之多对多
4.4.1 创建表
理解:
学生和课程是多对多的关系,一个学生可以对应多个课程,一个课程可以被多个学生选;
学生表 => 课程表(一个学生可以选择多个课程):一对多
课程表 => 学生表(一个课程可以被多个学生选择 ):一对多
创建学生表、课程表、课程学生中间表:
CREATE TABLE `course` (
`cid` INT(10) NOT NULL AUTO_INCREMENT,
`cname` VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (`cid`)
);
INSERT INTO `course`(`cid`,`cname`) VALUES
(1,'java'),(2,'css'),(3,'js'),(4,'c++'),(5,'php'),(6,'语文'),(7,'数学');
CREATE TABLE `student` (
`sid` INT(10) NOT NULL AUTO_INCREMENT,
`NAME` VARCHAR(50) DEFAULT NULL,
PRIMARY KEY (`sid`)
);
INSERT INTO `student`(`sid`,`NAME`) VALUES
(1,'张三'),(2,'李四'),(3,'柳岩'),(4,'小猪'),(5,'王宝强'),(6,'李晨');
CREATE TABLE `student_course` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
`sid` INT(10) DEFAULT NULL,
`cid` INT(10) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `fk_sid` (`sid`),
KEY `fk_cid` (`cid`),
CONSTRAINT `fk_cid` FOREIGN KEY (`cid`) REFERENCES `course` (`cid`),
CONSTRAINT `fk_sid` FOREIGN KEY (`sid`) REFERENCES `student` (`sid`)
);
INSERT INTO `student_course`(`id`,`sid`,`cid`) VALUES
(1,1,2),(2,2,1),(3,3,1),(4,4,1),(5,5,1),(6,1,3),(7,1,4),(8,2,5),(9,2,7);
4.4.2 创建表对应的实体类
学生实体类
package com.sy.pojo;
import java.util.List;
public class Student {
private Integer sid;
private String name;
//放置学生选择的课程数据
private List<Course> courseList;
public List<Course> getCourseList() {
return courseList;
}
public void setCourseList(List<Course> courseList) {
this.courseList = courseList;
}
public Student() {
}
public Student(Integer sid, String name) {
this.sid = sid;
this.name = name;
}
public Integer getSid() {
return sid;
}
public void setSid(Integer sid) {
this.sid = sid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", name='" + name + '\'' +
", courseList=" + courseList +
'}';
}
}
课程实体类
package com.sy.pojo;
import java.util.List;
public class Course {
private Integer cid;
private String cname;
private List<Student> studentList;
public List<Student> getStudentList() {
return studentList;
}
public void setStudentList(List<Student> studentList) {
this.studentList = studentList;
}
public Course() {
}
public Course(Integer cid, String cname) {
this.cid = cid;
this.cname = cname;
}
public Integer getCid() {
return cid;
}
public void setCid(Integer cid) {
this.cid = cid;
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
@Override
public String toString() {
return "Course{" +
"cid=" + cid +
", cname='" + cname + '\'' +
", studentList=" + studentList +
'}';
}
}
4.4.3 方法一: 嵌套结果方式
4.4.3.1 创建StudentMapper接口

4.4.3.2 创建StudentMapper.xml映射文件

4.4.3.3 测试程序

4.4.4 方法二: 嵌套查询方式(根据学生姓名查询学生选择的课程)
4.4.3.1 创建CourseMapper接口
创建根据学生的sid查询学生选择的课程的方法:

4.4.3.2 创建CourseMapper.xml映射文件

4.4.3.3 创建StudentMapper接口

4.4.3.4 创建StudentMapper.xml映射文件

4.4.3.5 测试程序

五、延迟加载策略
5.1 延迟加载的简介
什么是延迟加载?
延迟加载(lazy load)是(也称为懒加载)关联关系对象默认的加载方式,延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。
延迟加载,可以简单理解为,只有在使用的时候,才会发出sql语句进行查询。
5.2 为什么要使用延迟加载
减少访问数据库的频率,我们要访问的数据量过大时,明显用缓存不太合适,因为内存容量有限为了减少并发量,减少系统资源的消耗。
5.3 配置延迟加载策略,并测试结果
在mybatis中使用resultMap来实现一对一,一对多,多对多关系的操作。主要是通过 association、collection 实现一对一及一对多映射。association、collection 具备延迟加载功能。
延迟加载只有在嵌套查询方式中才能有效
5.3.1 局部延迟加载
修改OrderMapper.xml配置文件
把之前写的OrderMapper.xml中,嵌套查询方式,查询所有订单以及订单对应的用户的statement进行修改:
<select id="findOrderUserResultMap02" resultMap="orderUserResultMap02">
SELECT * FROM `orders`
</select>
<!-- 使用resultMap设置结果集和实体类中属性的映射关系 -->
<resultMap id="orderUserResultMap02" type="order">
<!--两部分数据: 订单 + 用户信息-->
<id column="id" property="id"></id>
<result column="user_id" property="userId"></result>
<result column="number" property="number"></result>
<result column="createtime" property="createtime"></result>
<result column="note" property="note"></result>
<!--
fetchType="lazy": 给用户的查询加入延迟加载, fetchType的默认值是eager,
加入延迟加载后, 只要用到订单对应的用户数据的时候, 才会去查询用户数据,
不加入延迟加载, 只要是查询订单就会查询对应的用户数据
-->
<association property="user" javaType="user" fetchType="lazy"
select="com.sy.mapper.UserMapper.findUserById"
column="user_id">
</association>
</resultMap>
配置文件对应的接口:
/**
* 查询所有订单信息, 以及订单对应的用户信息(嵌套查询实现)
* @return
*/
List<Order> findOrderUserResultMap02();
总结:
相关联的查询标签上加 fetchType="lazy"
fetchType默认值为eager ;立即加载,Lazy为延时加载。
延迟加载只有在嵌套查询方式中才能有效
5.3.2 全局延迟加载
如果希望所有关联都需要延时加载,可以在mybatis的核心配置文件中进行配置,不用在collection或association中指定。默认全局开启。
<settings>
<!-- 全局开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 当启用时,有会话时,会执行延迟加载 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
六、MyBatis 缓存
6.1 缓存简介
缓存是存在内存中的临时数据。将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。使用缓存能够减少和数据库的交互次数,减少系统开销,提高系统效率。经常查询并且不经常改变的数据能够使用缓存。
MyBatis系统中默认定义了两级缓存:
一级缓存:默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)
二级缓存:二级缓存需要手动开启和配置,它是基于Mapper.xml级别的缓存。
6.2 一级缓存
6.2.1 一级缓存介绍
mybatis一级缓存一种是SESSION级别的,针对同一个会话SqlSession中,执行多次条件完全相同的同一个sql,那么会共享这一缓存。
一级缓存mybatis已经为我们自动开启,不用我们手动操作,而且我们是关闭不了的!!但是我们可以手动清除缓存。
特点:
(1)自带的,不能卸载,但是我们可以手动清除缓存。
(2)SqlSession级别的缓存,使用无需配置。
6.2.2 测试一级缓存的清空
当我们在两次查询之间做增、删、改操作都会把一级缓存清空,因为不清空就不能保证缓存中的数据与数据库中数据的一致性,可能会读取不正确的数据。
总结:
一级缓存清空三种方式:
(1)clearCache()
(2)手动提交事务: commit();
(3)执行数据库的写操作: delete insert update
注意:
SqlSession对象, 它是应用程序与持久层之间执行交互操作的对象, 主要作用是执行持久化操作; 类似于JDBC中的Connection连接对象, SqlSession对象包含了执行SQL语句的操作方法, 由于底层封装了JDBC连接, 所以可以直接使用SqlSession对象执行以映射的SQL语句; SqlSession对象一旦关闭, 就代表Connection连接对象关闭, 查询结束, 再次查询就会报错;
如果当前的SqlSession对象一旦关闭, 想要再次执行查询方法, 需要重新创建SqlSession对象, 并执行查询方法即可, 如果两个不同的SqlSession对象, 我能不能也让它走缓存呢?
可以使用二级缓存;
6.2.3 测试程序
使用UserMapper接口中,根据用户id查询用户的方法缓存测试:

UserMapper.xml映射文件:

测试程序: 两次查询之间执行增删或者clearCache、commit都会清空一级缓存

结果展示:

6.3 二级缓存
6.3.1 二级缓存介绍
二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存,它是基于Mapper.xml文件级别的缓存,一个namespace命名空间对应一个二级缓存。
二级缓存工作机制为:
一个会话查询的一条数据就会被放在当前会话的一级缓存中;
如果当前会话关闭了,这个会话对应的一级缓存就没了;但是二级缓存开启后,一级缓存中的数据被保存到二级缓存中,新的会话查询信息,就可以从二级缓存中获取内容;
不同的mapper查出的数据会放在自己对应的缓存(map)中
总结:
二级缓存:默认是不开启的,使用需要配置。
二级缓存是 mapper 映射级别的缓存,多个 SqlSession 去操作同一个 Mapper 映射的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。
6.3.2 二级缓存的开启与关闭
在SqlMapConfig.xml 文件开启二级缓存

配置相关的 Mapper 映射文件

注意:对应的实体类记得实现Serializable接口,因为往二级缓存中存放数据需要序列化
配置 statement 中的 useCache 属性
测试程序:





1113

被折叠的 条评论
为什么被折叠?



