MyBatis
文章目录
- MyBatis
- [1. JDBC不足](#1. JDBC不足)
- [2. 为什么要学习框架](#2. 为什么要学习框架)
-
-
- [2.1.1 提高开发效率](#2.1.1 提高开发效率)
- [2.1.2 可以提高代码的健壮性](#2.1.2 可以提高代码的健壮性)
-
- [3. Mybatis概述](#3. Mybatis概述)
-
- [3.1 简介](#3.1 简介)
- [3.2 ORM](#3.2 ORM)
- [4. Mybatis入门使用](#4. Mybatis入门使用)
-
- [4.1 创建数据库和表](#4.1 创建数据库和表)
- [4.2 创建空项目](#4.2 创建空项目)
- [4.3 创建Maven项目及包结构](#4.3 创建Maven项目及包结构)
- [4.4 引入Mybatis相关jar包依赖](#4.4 引入Mybatis相关jar包依赖)
- [4.5 编写实体类Student](#4.5 编写实体类Student)
- [4.6 编写映射文件StudentMapper接口](#4.6 编写映射文件StudentMapper接口)
- [4.7 编写映射文件StudentMapper.xml](#4.7 编写映射文件StudentMapper.xml)
- [4.8 编写mybatis.xml核心配置文件](#4.8 编写mybatis.xml核心配置文件)
- [4.9 编写测试类](#4.9 编写测试类)
- [5. Mybatis集成SqlSessionUtil工具类](#5. Mybatis集成SqlSessionUtil工具类)
-
-
- [5.1.1 SqlSessionUtil工具类](#5.1.1 SqlSessionUtil工具类)
- [5.1.2 测试工具类](#5.1.2 测试工具类)
- [5.1.3 CRUD测试](#5.1.3 CRUD测试)
-
- [6. Dao概述及集成](#6. Dao概述及集成)
-
- [6.1 关于持久层的说明](#6.1 关于持久层的说明)
- [6.2 集成Dao](#6.2 集成Dao)
-
- [6.2.1 Dao接口和实现类](#6.2.1 Dao接口和实现类)
- [6.2.2 StudentMapper.xml](#6.2.2 StudentMapper.xml)
- [6.2.3 测试类](#6.2.3 测试类)
- [7. MyBatis的接口代理支持](#7. MyBatis的接口代理支持)
-
-
- [7.1.1 测试类](#7.1.1 测试类)
-
- [8. Mybatis核心配置文件详解](#8. Mybatis核心配置文件详解)
-
- [8.1 数据源配置详解](#8.1 数据源配置详解)
- [8.2 引入db.properties配置文件详解](#8.2 引入db.properties配置文件详解)
-
- [8.2.1 创建db.properties文件](#8.2.1 创建db.properties文件)
- [8.2.2 修改mybatis.xml文件](#8.2.2 修改mybatis.xml文件)
- [8.3 别名优化详解](#8.3 别名优化详解)
- [8.4 日志配置详解](#8.4 日志配置详解)
-
- [8.4.1 引入日志相关jar包](#8.4.1 引入日志相关jar包)
- [8.4.2 创建log4j.properties的日志配置文件](#8.4.2 创建log4j.properties的日志配置文件)
- [8.4.3 修改mybatis.xml配置文件](#8.4.3 修改mybatis.xml配置文件)
- [8.4.4 测试并查看E盘的logs目录](#8.4.4 测试并查看E盘的logs目录)
- [9. Mybatis结果映射&配置文件详解](#9. Mybatis结果映射&配置文件详解)
-
- [9.1 结果映射名称不一致方案](#9.1 结果映射名称不一致方案)
-
- [9.1.1 在映射文件中新增映射关系](#9.1.1 在映射文件中新增映射关系)
- [9.2 MyBatis获取参数值的两种方式:#和(重点)](#和(重点))
-
- [9.2.1. 区别](#9.2.1. 区别)
- [9.2.2 示例](#9.2.2 示例)
- 9.2.3 #示例
- [9.3 SQL公共片段](#9.3 SQL公共片段)
- [9.4 模糊查询(重点)](#9.4 模糊查询(重点))
-
- [9.4.1 方案一[推荐]](#9.4.1 方案一[推荐])
- [9.4.2 方案二[拼接]](#9.4.2 方案二[拼接])
- [9.4.3 方案三[bind]](#9.4.3 方案三[bind])
- [9.5 接收参数问题(重点)](#9.5 接收参数问题(重点))
- [9.6 动态SQL(重点)](#9.6 动态SQL(重点))
-
- [9.6.1 if标签](#9.6.1 if标签)
- [9.6.2 foreach](#9.6.2 foreach)
- [9.6.3 trim 标签](#9.6.3 trim 标签)
- [9.6.4 where](#9.6.4 where)
- [9.6.5 set标签](#9.6.5 set标签)
- [9.6.6. choose&when&otherwise标签](#9.6.6. choose&when&otherwise标签)
- [9.6.7. 特殊符号处理](#9.6.7. 特殊符号处理)
- [9.6.8. sql标签](#9.6.8. sql标签)
- [10. Mybatis注解](#10. Mybatis注解)
-
- [10.1 Mybatis注解](#10.1 Mybatis注解)
- [11. MyBatis多种结果封装](#11. MyBatis多种结果封装)
-
- 12.1.一条结果封装
- 12.2.多条结果封装
- [12.3. 模板配置](#12.3. 模板配置)
- [12. 多表查询处理](#12. 多表查询处理)
-
- [12.1 创建测试表](#12.1 创建测试表)
- [12.2 一对多 (嵌套结果查询)](#12.2 一对多 (嵌套结果查询))
- [12.3 一对多(嵌套语句查询)](#12.3 一对多(嵌套语句查询))
- [12.3 多对一 (嵌套结果查询)](#12.3 多对一 (嵌套结果查询))
- [12.4 多对一(嵌套语句查询)](#12.4 多对一(嵌套语句查询))
- [12.4 多对多](#12.4 多对多)
- [13. 延迟加载](#13. 延迟加载)
-
- [13.1. 概述](#13.1. 概述)
- [13.2. 开启延迟加载](#13.2. 开启延迟加载)
- 13.3.局部立即加载
- [14. Mybatis缓存](#14. Mybatis缓存)
1. JDBC不足
JDBC作为Java操作数据库的模板,如果想要对数据库进行操作,必须使用JDBC,但是在使用JDBC进行数据库操作时,重复代码多,动态SQL构建繁琐,将查询结果转化为对象,相当麻烦,开发效率低。
基于JDBC开发效率相对低的情况,市面上各个组织,对JDBC进行封装,产生各种数据库操作层解决方案:
Hibernate 重量级的ORM框架
ibatis 轻量级ORM框架 与2010-06-16 改名 mybatis
Spring JPA
Mybatis plus
Spring JDBCTemplate
以上框架都是对JDBC的封装,处理Java操作数据库数据的问题。
2. 为什么要学习框架
2.1.1 提高开发效率
在Java中,框架在一定程度就是对某些功能的封装,对外暴露统一操作API,可以简化开发难度,提高开发效率。
2.1.2 可以提高代码的健壮性
市面上相对比较流行的框架,被大多人使用,出现的问题能够及时暴露,问题也能得到较快的修复。
3. Mybatis概述
3.1 简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
Mybtais是一个ORM框架,轻量级的ORM框架。相对于重量级的ORM框架Hibernate而言,mybatis是一个半自动框架,而Hibernate是一个全自动框架。
Mybatis是一个半自动的框架,早期Hibernate在流行时,开发者发现Hibernate虽然功能强大,但是由于如果想使用全自动功能,将Hibernate和数据库关心进行配置,配置很繁琐,其二,Hibernate对功能进行全方面的封装,将用户的操作,转化为SQL语句,然后进行数据库操作,整个转换过程是Hibernate,开发无法控制,如果要进行SQL语句优化是没法实现的。所以,在数据库压力逐渐增大的情况下,Hibernate框架性能问题就出现了。基于这样的情况,Mybatis框架应运而生,mybatis将SQL语句的定义控制权,完全交给了开发者,并且暴露一套API,对JDBC中:事务,参数,查询结果等进行配置处理。Mybatis也是基于ORM思想.
3.2 ORM
ORM : Object relation mapping
对象关系映射
将数据库信息和Java中实体类进行映射
4. Mybatis入门使用
4.1 创建数据库和表




4.2 创建空项目



4.3 创建Maven项目及包结构

4.4 引入Mybatis相关jar包依赖
xml
<dependencies>
<!--引入mybatis的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!--引入mysql的驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>
</dependencies>
4.5 编写实体类Student
java
public class Student {
private String id;
private String name;
private int age;
public Student(){}
public Student(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
4.6 编写映射文件StudentMapper接口
java
package org.example.mapper;
import org.example.entity.Student;
import java.util.Map;
public interface StudentMapper {
Student getById(String id);
}
4.7 编写映射文件StudentMapper.xml
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="org.example.mapper.StudentMapper">
<select id="getById" resultType="org.example.entity.Student">
select * from t_student where id = #{id}
</select>
</mapper>
4.8 编写mybatis.xml核心配置文件
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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false&characterEncoding=UTF8&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
</configuration>
4.9 编写测试类
java
package org.example.test;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.example.entity.Student;
import java.io.InputStream;
import java.util.Map;
public class Test01 {
public static void main(String[] args) throws Exception{
//1.引入配置文件
String config = "mybatis.xml";
InputStream in = Resources.getResourceAsStream(config);
//2.解析配置文件 构建 SqlSession 构建对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//3.使用构建对象构建 SqlSessionFactory 对象 创建Sql会话对象 程序和数据库进行SQL会话对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(in);
//4.创建一个具体的SqlSession 对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//5.进行数据库具体sql会话
Student s = sqlSession.selectOne("org.example.mapper.StudentMapper.getById","A0001");
System.out.println(s);
//6.关闭会话
sqlSession.close();
}
}
注意:mapper映射文件的命名空间,必须是所对应接口的全路径(先记住,后面有用)

5. Mybatis集成SqlSessionUtil工具类
新建项目

5.1.1 SqlSessionUtil工具类
java
package org.example.utils;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class SqlSessionUtil {
private static SqlSessionFactory sqlSessionFactory = null;
static {
try {
InputStream is = Resources.getResourceAsStream("mybatis.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSession () {
return sqlSessionFactory.openSession();
}
public static void close(SqlSession session){
if(null != session){
session.close();
}
}
}
5.1.2 测试工具类
java
public class Test01 {
public static void main(String[] args) {
SqlSession session = SqlSessionUtil.getSession();
Student s = session.selectOne("org.example.mapper.StudentMapper.getById", "A0001");
System.out.println(s);
SqlSessionUtil.close(session);
}
}
5.1.3 CRUD测试
更新mapper映射文件
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="org.example.mapper.StudentMapper">
<select id="getById" resultType="org.example.entity.Student">
select * from t_student where id = #{id}
</select>
<insert id="insert">
insert into t_student(id,name,age) values(#{id},#{name},#{age})
</insert>
<update id="update">
update t_student set name=#{name},age=#{age} where id=#{id}
</update>
<delete id="delete">
delete from t_student where id=#{id}
</delete>
<select id="getAll" resultType="org.example.entity.Student">
select * from t_student
</select>
</mapper>
注意:添加修改删除操作,需要手动提交事务
测试:
java
public static void main(String[] args) {
SqlSession session = SqlSessionUtil.getSession();
/*Student s = session.selectOne("org.example.mapper.StudentMapper.getById", "A0001");
System.out.println(s);*/
/*Student s = new Student("A0005","sq",27);
session.insert("org.example.mapper.StudentMapper.insert", s);
session.commit();*/
/*Student s = new Student("A0005","sq1",17);
session.update("org.example.mapper.StudentMapper.update", s);
session.commit();*/
/*session.delete("org.example.mapper.StudentMapper.delete", "A0005");
session.commit();*/
List<Student> sList = session.selectList("org.example.mapper.StudentMapper.getAll");
System.out.println(sList);
SqlSessionUtil.close(session);
}
6. Dao概述及集成
6.1 关于持久层的说明
6.2 集成Dao
6.2.1 Dao接口和实现类
java
接口
public interface StudentMapper {
Student getById(String id);
void insert(Student s);
void update(Student s);
void delete(String id);
List<Student> getAll();
}
实现类
public class StudentMapperImpl implements StudentMapper {
@Override
public Student getById(String id) {
SqlSession session = SqlSessionUtil.getSession();
Student s = session.selectOne("org.example.mapper.StudentMapper.getById", id);
SqlSessionUtil.close(session);
return s;
}
@Override
public void insert(Student s) {
SqlSession session = SqlSessionUtil.getSession();
session.insert("org.example.mapper.StudentMapper.insert", s);
SqlSessionUtil.close(session);
}
@Override
public void update(Student s) {
SqlSession session = SqlSessionUtil.getSession();
session.update("org.example.mapper.StudentMapper.update", s);
SqlSessionUtil.close(session);
}
@Override
public void delete(String id) {
SqlSession session = SqlSessionUtil.getSession();
session.delete("org.example.mapper.StudentMapper.delete", id);
SqlSessionUtil.close(session);
}
@Override
public List<Student> getAll() {
SqlSession session = SqlSessionUtil.getSession();
List<Student> sList = session.selectList("org.example.mapper.StudentMapper.getAll");
SqlSessionUtil.close(session);
return sList;
}
}
6.2.2 StudentMapper.xml
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="org.example.mapper.StudentMapper">
<select id="getById" resultType="org.example.entity.Student">
select * from t_student where id = #{id}
</select>
<insert id="insert">
insert into t_student(id,name,age) values(#{id},#{name},#{age})
</insert>
<update id="update">
update t_student set name=#{name},age=#{age} where id=#{id}
</update>
<delete id="delete">
delete from t_student where id=#{id}
</delete>
<select id="getAll" resultType="org.example.entity.Student">
select * from t_student
</select>
</mapper>
6.2.3 测试类
java
public static void main(String[] args) {
StudentMapper studentMapper = new StudentMapperImpl();
Student s1 = studentMapper.getById("A0001");
System.out.println(s1);
}
7. MyBatis的接口代理支持
使用代理目的:不用手写Dao实现类(只写接口即可)
干掉Dao实现类

代理要求:
(1) 接口名和mapper映射文件名保持一致
(2) 接口中的方法数量,方法名,参数,返回值类型,必须和mapper映射文件中的SQL保持一致
(3) mapper映射文件的命名空间,必须是接口的全路径
(4) 通过SqlSession的API getMapper来取得实现类对象
7.1.1 测试类
java
public class Test01 {
public static void main(String[] args) {
SqlSession session = SqlSessionUtil.getSession();
StudentMapper studentMapper = session.getMapper(StudentMapper.class);
Student s1 = studentMapper.getById("A0001");
System.out.println(s1);
SqlSessionUtil.close(session);
}
}
8. Mybatis核心配置文件详解
8.1 数据源配置详解
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>
<environments default="development">
<environment id="development">
<!--基于 JDBC 的事务机制来管理事务-->
<transactionManager type="JDBC" />
<!-- 配置数据源 type="POOLED表示mybatis自带的简单连接池-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false&characterEncoding=UTF8&serverTimezone=UTC" />
<property name="username" value="root" />
<property name="password" value="123456" />
</dataSource>
</environment>
</environments>
<!-- 注册mapper文件 -->
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
8.2 引入db.properties配置文件详解
8.2.1 创建db.properties文件
properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useUnicode=true&useSSL=false&characterEncoding=UTF8&serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
8.2.2 修改mybatis.xml文件
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>
<!--外部配置文件配置-->
<properties resource="db.properties"></properties>
<!--环境配置-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
8.3 别名优化详解
【注意】:别名配置必须是在enviroments****标签的上面配置
xml
typeAliases>
<!--为指定类型指名 别名 使得在mapper映射文件中可以简化引用-->
<!--<typeAlias type="org.example.entity.Student" alias="stu"/>-->
<!-- 为某个包下的所有类指定别名 默认别名是对应的类名 -->
<package name="org.example.entity"/>
</typeAliases>
8.4 日志配置详解
日志,就是将程序运行时,一些关键信息进行保存文件中的操作.当问题发生时,是无法观察控制台,将关键的信息保存在文件,便于后期检查。
日志主要分为以下级别:
debug调试模式时进行记录的调试信息
info具体信息
error 发生异常时可以进行记录的方法
debug级别最低
info级别高于DEBUG
error级别最高
8.4.1 引入日志相关jar包
xml
<!-- 日志相关依赖 -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
8.4.2 创建log4j.properties的日志配置文件
properties
# 全局日志配置
log4j.rootLogger=DEBUG, stdout,D,E
# MyBatis 日志配置
log4j.logger.org.mybatis.example.BlogMapper=TRACE
# 控制台输出
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = E://logs/log.log
log4j.appender.D.Append = true
log4j.appender.D.Threshold = DEBUG
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File =E://logs/error.log
log4j.appender.E.Append = true
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
# 设置日志级别
log4j.rootLogger=DEBUG, stdout
# 控制台输出日志
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p %c - %m%n
# MyBatis 日志
log4j.logger.org.mybatis=DEBUG
8.4.3 修改mybatis.xml配置文件
xml
<settings>
<setting name="logImpl" value="LOG4J"/>
</settings>
8.4.4 测试并查看E盘的logs目录

9. Mybatis结果映射&配置文件详解
9.1 结果映射名称不一致方案
在mybatis中,默认将查询的结果返回一个实体类,查询结果和实体类根据结果列别名和类中属性名一一对应。当查询结果列别名和类属性名不一致。
解决方案有两种:
-
将别名修改为一致
-
使用mybatis内置结果映射处理器
9.1.1 在映射文件中新增映射关系
- resultMap:自定义封装,自定义映射,标识返回值类型,只不过映射关系需要我们自己定义
- resultType:mybatis自动封装,自动映射,标识返回值的类型是谁,他必须要求数据库的字段名称与实体类的字段名称一致才能映射的上
- type 属性: 告诉Mybatis把自定义映射封装到那个对象中
- property: 指定实体类中的那个字段做映射
- column:指定表中那一列做映射
- type 属性: 告诉Mybatis把自定义映射封装到那个对象中

9.2 MyBatis获取参数值的两种方式:#和$(重点)
- 如果有学过IDBC的同学应该知道,在IDBC中我们可以使用两种方式进行SQL语句参数的赋值,一种是SQL语句拼接参数,一种是使用占位符,那么其实在MyBatis中${}和#(}就是对应这两种方式
- 之前在映射文件中,我们讲过可以通过两种方式获取到mapper接口的参数值。$0:本质上是字符串拼接,如果参数类型是字符串或者是时间类型那么需要手动添加单引号。#(:本质上是占位符赋值,也被称为预编译,如果参数类型是字符串或者是时间类型那么会自动加单引号
sql
Long id = 1:
select*fromt employee where id ='${id}'
"select* from t employee where id =" + "1'
9.2.1. 区别
- #{}行
- #{} 解析为一个预编译语句的参数标记符,一个 # {}被解析为一个参数占位符
- SQL语句:"SELECT * FROM t_employee where name = #{name}""
- *预编译语句为:"SELECT * FROMt employee where name=?"
- #0} 解析之后会将String类型或者时间类型的数据自动加上引号,其他数据类型不会。
- #{} 很大程度上可以防止SQL注入
- #{} 解析为一个预编译语句的参数标记符,一个 # {}被解析为一个参数占位符
- ${}
- ${}仅仅为一个纯碎的 string 替换,在动态 SQL解析阶段将会进行变量替换
- "SQL语句:"SELECT * FROMt employee where name =""+name+""
- ${} 解析之后是什么就是什么,他不会当做字符串处理,所以如果是字符串或者时间类型需要我们手动加上单引号。
- ${} 主要用于SQL拼接的时候,有很大的SQL注入隐患。
- ${}仅仅为一个纯碎的 string 替换,在动态 SQL解析阶段将会进行变量替换
- 在某些特殊场合下只能用${},不能用#{}
- 例如:在使用排序时ORDER BY${id},如果使用#(id),则会被解析成ORDER BY"id",这显然是一种错误的写法
- SQL注入
- 如果使用拼接的方式,那么我们就是在条件后面拼接参数值,那么如果是下面这种情况,就会出现SOL注入问题了,不管我是否知道具体的条件,我都可以通过拼接OR1=1得到数据,这就是SQL注入。
- name值为:123 0R1=1
- SQL语为:"SELECT * FROM t employee where name =""+username+""
- 最终运行时:"SELECT * FROM temployee where name=123 OR1=1"
- 如果使用拼接的方式,那么我们就是在条件后面拼接参数值,那么如果是下面这种情况,就会出现SOL注入问题了,不管我是否知道具体的条件,我都可以通过拼接OR1=1得到数据,这就是SQL注入。
9.2.2 $示例
xml
<select id="getById" resultType="student">
select * from t_student where id = ${id}
</select>
测试:studentMapper.getById("'A0001'");
注意:由于是字符串拼接,所以A0001需要套在引号里面

9.2.3 #示例
xml
<select id="selectUser" resultType="com.example.domain.User">
select * from user where id = #{id}
</select>

9.3 SQL公共片段
在开发中,需要书写大量的SQL语句,并且这些SQL数据很大部分内容是重复内容,基于这样的情况,mybatis提供模板,可以在模板中定义需要使用sql语句的部分内容,然后在需要使用到这个部分内容的地方直接引入。
使用sql标签,定义SQL片段,使用include引入,sql片段
xml
<sql id="BASE_COLUMN">
id,name,pwd
</sql>
<!-- 查询单个用户 -->
<select id="selectUser" resultType="com.example.domain.User">
select
<include refid="BASE_COLUMN">
</include>
from user where id = #{id}
</select>
注意:
- 在sql片段中 id,是这个sql片段的唯一标识
- 在引入时,include标签中,refid关联sql片段的id
9.4 模糊查询(重点)
在mybatis中,模糊查询主要有3种方式:
- **like concat('%',**关键字,'%') 推荐
- like '%关键字%'
- mybatis推荐的bind标签
9.4.1 方案一[推荐]
xml
<!-- 查询一个 -->
<select id="selectByName" resultMap="BaseResultMap">
select
<include refid="BASE_COLUMN" />
from user where name like concat('%',#{name},'%')
</select>
9.4.2 方案二[拼接]
使用$存在sql注入攻击安全问题
xml
<select id="selectByName" resultMap="BaseResultMap">
select
<include refid="BASE_COLUMN" />
from user where name like '%${name}%'
</select>
9.4.3 方案三[bind]
xml
<select id="selectByName" resultMap="BaseResultMap">
<bind name="keyWord" value="new String('%'+name+'%')"/>
select
<include refid="BASE_COLUMN" />
from user where name like #{keyWord}
</select>
9.5 接收参数问题(重点)
9.5.1.参数为单个字面量
- 当参数为单个字面量时,映射文件中获取值的名称叫什么无所谓,但是规范写法是与mapper层名称保持一致
java
Employee selectByName(String name);
xml
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{aaa}
</select>
9.5.2.参数为多个字面量
- 当参数为多个字面量时,映射文件中获取值的名称与mapper层名称保持一致,会报错
java
Employee selectByNameAndAge(String name, Integer age);
xml
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{name} and age=#{age}
</select>

-
arg1,arg0,param1,param2\]的原因:因为我们传递了两个参数,MyBatis会将两个参数以两组Key、Value进行存储
- 值:"苹果",12
- map.put("arg0","苹果");
- map.put("atg1","12");
- map.put("param1","苹果");
- map.put("param2","12");
xml
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{arg0} and age=#{arg1}
</select>
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{param1} and age=#{param2}
</select>
9.5.3.@Param注解方式
java
Employee selectByNameAndAge(@Parm("name")String name, @Parm("age")Integer age);
xml
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{name} and age=#{age}
</select>
9.5.4.参数为Map集合
java
Employee selectByMap(Map<String,0bject> map);
//map.put("name","苹果");
//map.put("age","10");
xml
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{name} and age=#{age}
</select>
9.5.5.参数为实体类对象
- 获取值对象中获取值其实就是写对象中的字段名称,底层会调用对象的get方法获取到字段的值,所以对象中该字段必须有get方法
- 如果参数是对象时a,mybatis会将你给定的名称前面加上get,再将你给定的名称首字母大写,去调用你传递进来的对象此方法,从而获取到对象的字段值getName,所以对象中该字段必须有get方法,否则获取不到字段值。

java
@Date
public Class Employee{
String name;
Integer age;
}
java
Employee selectByNameAndAge(@Parm("emp")Employee emp);
xml
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{emp.name} and age=#{emp.age}
</select>
9.5.6.参数为集合或者数组
--- 使用循环
9.6 动态SQL(重点)
动态SQL是指根据不同的参数,产生不同的SQL语句。这样的SQL就是动态SQL。
Mybatis提供了一下标签,用于动态SQL的生成:
-
if
-
foreach
-
where
-
set
-
trim
9.6.1 if标签
在if标签中,test属性是必须有,test属性值是一个表达式,如果表达式值为true,则if标签包裹的内容会拼接在当前sql上。
and 并且
or 或者
== 等于
!= 不等于
示例:
xml
<!-- 条件查询 -->
<select id="selectList" resultType="com.example.domain.User">
select <include refid="BASE_COLUMN" /> from user where 1=1
<if test="name != null and name !=''">
and name like concat('%',#{name},'%')
</if>
</select>
9.6.2 foreach
循环标签,循环标签多用于批量操作。
例如:批量新增,批量删除等等
-
collection 待循环的容器
-
item 指代 每次循环容器中的元素
-
open 开始循环是拼接字符串
-
close 循环结束拼接字符串
-
separator 每次循环之间拼接的字符串
-
index 循环索引
xml
<delete id="batchDelete">
delete from t_student where id in
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
注意:collection="list" 命名是list
9.6.3 trim 标签
- 标签的功能是可以实现代替其他标签的功能,但同时又比其他标签更加灵活,他有四个属性
- oprefix:指定要动态添加的前缀
- osuffix属性:指定要动态添加的后缀
- oprefixOverrides:去除sql语句前面的关键字或者字符,假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND",使用"|分隔有可能的多个值
- suffixOverrides属性:指定要动态去掉的后缀,使用"|"分隔有可能的多个值
9.6.4 where
在mybatis中,存在sql条件,当有多个sql条件时,需要处理and关键字问题,因为where后面的第一个条件不需要and,解决方案:
l 在where后面 拼接 1=1 类似的条件,这样其他条件都不是第一个条件,都需要拼接and
l mybatis 提供了where标签,取代where关键字,默认去掉第一个条件 and
注意:
建议,只在查询语句中使用where标签,因为当where标签中的条件都不成立时,会没有where关键字。
xml
<select id="selectList2" parameterType="map" resultMap="BaseResultMap" >
select <include refid="BASE_COLUMN" /> from user
<where>
<if test="name != null and name !=''">
and name like concat('%',#{name},'%')
</if>
<if test="sex != null and sex !=''">
and sex = #{sex}
</if>
</where>
</select>
<!-- 反面例子 如果name 值是空字符串 会怎么样?? -->
<delete id="deleteByName" >
delete from user
<where>
<if test="name != null and name !=''">
name = #{name}
</if>
</where>
</delete>
9.6.5 set标签
- set标签是在我们进行update操作时使用,可以解决修改条件不确定时引发的问题,通常和if标签搭配使用,它有两个功能
- 如果set标签中的条件成立,那么会自动加上set关键字
- 会自动去掉标签中条件语句中多余的逗号。
xml
<update id="updateUserById">
update user
<set>
<if test="name != null and name !=''">
name = #{name},
</if>
<if test="pwd != null">
pwd = #{pwd},
</if>
</set>
where id = #{id}
</update>
9.6.6. choose&when&otherwise标签
- choose标签实现多路选择,当choose下的when标签条件满足时,就将when中的sql拼进外面的sql,反之若不满足,则将下面的otherwise标签中的sql拼进总sql
- 这三个关键字是一套组合标签,类似于ava中的if()else if()else{} 的功能
- choose是父标签,when和otherwise标签需要写在此标签下
- when相当于 if(){}else if(){}
- otherwise相当于最后的 else{}
9.6.7. 特殊符号处理
- 在sql语句中,我们会使用到一些比较符号,在xml中需要特殊处理
- 注意:在xml中特殊字符不处理,会报错
- 在xml的转义字符
- 符号:<、<=、>、>=、&、
- "gt:大于
- "lt:小于
- "get:大于等于
- let:小于等于
- 或者可以用CDATA代码段。
- 大于等于
<![CDATA[ >= ]]> - 小于等于
<![CDATA[ <= ]]>
- 大于等于
- 在xml的转义字符
9.6.8. sql标签
在java代码中,经常使用到的代码,我们会将他抽取为一个方法,在需要使用的地方引入,那么在SQL中也可以将重复的SQL代码块进行抽取,然后引入使用


10. Mybatis注解
10.1 Mybatis注解
mybatis配置虽然相对简单,但是还是麻烦。所以,mybatis为了简化配置,也提供了注解。
常用注解:
- @Select
- @Insert
- @Delete
- @Update
java
public interface UserMapper {
@Insert("insert into user (name,pwd) value (#{name},#{pwd})")
Integer insert(User user);
@Delete("delete from user where id = #{id}")
Integer delete(Integer id);
@Select("select id,name,pwd from user ")
List<User> selectAll();
/* 动态sql 写法 了解*/
@Select("<script>select id,name,pwd from user <where> <if test='id != null'>id > 18</if> </where></script>")
List<User> selectList(@Param("age") Integer age);
}
11. MyBatis多种结果封装
12.1.一条结果封装
12.1.1.对象封装结果
java
Employee selectByName(String name);
xml
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{name}
</select>
12.1.2.List封装结果
java
## 当查询结果为一条时,使用List<对象>进行接收
List<Employee> selectByName(String name);
xml
<select id="selectByName" resultType="Employee">
select*fromt_employee where name = #{name}
</select>
12.1.3.Map封装结果
java
##当查询结果为一条时,使用Map<String,Object>进行接收
Map<String,Object> selectByName(String name);
xml
<select id="selectByName" resultType="map">
select*fromt_employee where name = #{name}
</select>

12.1.4.使用@MapKey注解
- 以指定的字段作为key
java
@MapKey("id")
Map<String,0bject> selectByNameToMapKey(String name);
xml
<select id="selectByNameToMapKey" resultType="map">
select*fromt_employee where name = #{name}
</select>

12.2.多条结果封装
12.2.1.List封装结果
12.2.2.Map封装结果
和上面一样
12.3. 模板配置



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="org.example.mapper.XXX">
</mapper>
12. 多表查询处理
12.1 创建测试表
sql
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sname` varchar(255) DEFAULT NULL,
`cid` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `classes` (
`id` int(11) NOT NULL,
`cname` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 班级表数据:
INSERT INTO `classes` VALUES (1, '三年一班');
INSERT INTO `classes` VALUES (2, '三年二班');
INSERT INTO `classes` VALUES (3, '三年三班');
-- 学生表数据:
INSERT INTO `student` VALUES (1, 'zs', 1);
INSERT INTO `student` VALUES (2, 'ls', 1);
INSERT INTO `student` VALUES (3, 'ww', 1);
INSERT INTO `student` VALUES (4, 'zl', 2);
INSERT INTO `student` VALUES (5, 'sq', 2);
java
package com.example.domain;
import lombok.Data;
@Data
public class Student {
private Integer id; //学生ID
private String sname;//学生名称
private Integer cid;//班级ID
}
java
package com.example.domain;
import lombok.Data;
import java.util.List;
//一对多,所以主表在1的一方
@Data
public class Classes {
private Integer id;//班级ID
private String cname;//班级名称
private List<Student> students; //学生信息
}
12.2 一对多 (嵌套结果查询)
- 一对多,主表就是1的一方,从表就是多的一方
- 嵌套结果只需要执行下面这一条SQL语句即可
创建ClassesMapper,在mybatis.xml中注册
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="org.example.mapper.ClassesMapper">
<!-- 一对多嵌套结果查询,必须使用resultMap自定义映射,以前如何封装主表字段,现在还是怎么封装
collection:此标签专用来做集合字段的结果封装
property:表示把集合封装到返回的主表实体类的那个字段中 -- 对应我们的students; //学生信息字段
ofType表示把集合封装到返回的主表实体类的那个字段中的泛型(类型)是什么 -- 对应我们的students; //学生信息字段Student类型
-->
<resultMap id="CLASSES_STUDENT_MAP" type="Classes">
<!--封装主表数据字段-->
<result column="id" property="id" />
<result column="cname" property="cname" />
<!--封装从表数据字段-->
<collection property="students" ofType="Student">
<!--在collection中正常映射从表字段即可,还是使用id、result -->
<result column="id" property="id" />
<result column="sname" property="sname" />
<result column="id" property="id" />
</collection>
</resultMap>
<select id="selectAll" resultMap="CLASSES_STUDENT_MAP">
select c.id ,c.cname,s.id sid,s.sname from classes c left join student s on c.id = s.cid
</select>
</mapper>
java
package com.example.mapper;
import com.example.domain.Classes;
import java.util.List;
public interface ClassesMapper {
List<Classes> selectAll();
}
java
package com.example.test;
import com.example.domain.Classes;
import com.example.mapper.ClassesMapper;
import com.example.util.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class Test {
public static void main(String[] args) {
SqlSession session = SqlSessionUtil.getSession();
ClassesMapper mapper = session.getMapper(ClassesMapper.class);
List<Classes> classes = mapper.selectAll();
System.out.println(classes);
session.commit();
session.close();
}
}
12.3 一对多(嵌套语句查询)
- 联套语句需要执行 1+N条,因为套语句的关联查询是分开的

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="org.example.mapper.ClassesMapper">
<!-- 一对多嵌套语句查询,必须使用resultMap自定义映射,以前如何封装主表字段,现在还是怎么封装。
select:专门用来调用执行SQL语句的,resultMap每一次封装,都会调用一次SQL语句
注意:如果要调用的SQL语句是在当前xml,那么直接写要调用的SOL语句的ID即可
如果要调用的是其他xml中的SQL语句,那么就需要写上namespace+id,进行全路径调用
column:
1.在自定义映射中指向到数据库表的字段
2.在collection标签中就是用来传递参数的,此属性的值就是自定义映射中你要传递的数据库字段名称
-->
<resultMap id="CLASSES_STUDENT_MAP" type="Classes">
<!--封装主表数据字段-->
<result column="id" property="id" />
<result column="cname" property="cname" />
<!--查询从表数据-->
<collection property="students" select="selectById" column="id" />
</resultMap>
<select id="selectAll" resultMap="CLASSES_STUDENT_MAP">
select * from classes
</select>
<select id="selectById" resultType="Student">
select * from student WHERE cid =#{id}
</select>
</mapper>

12.3 多对一 (嵌套结果查询)
- 多对一:主表就是多的一方,从表就是一的一方
java
package com.example.domain;
import lombok.Data;
// 多的一方
@Data
public class Student {
private Integer id; //学生ID
private String sname;//学生名称
private Integer cid;//班级ID
private Classes classes;//班级信息
}
java
package com.example.domain;
import lombok.Data;
import java.util.List;
@Data
public class Classes {
private Integer id;//班级ID
private String cname;//班级名称
}
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="org.example.mapper.StudentMapper">
<resultMap id="STUDENT_CLASSES_MAP" type="Student">
<result column="id" property="id" />
<result column="sname" property="sname" />
<result column="cid" property="cid" />
<!-- mybatis 提供的关联标签 多对一
property :当前类中关联属性
javaType : 属性所属的类
column="cid":表示数据库表中的列名为 cid。
property="id":表示 Java 对象 Classes 中的属性名为 id。
这样配置后,MyBatis 会将 student 表中的 cid 列的值映射到 Student 对象的 classes 属性的 id 字段中。接下来的cname等非主键字段,就是classes表自身的映射了。-->
<association property="classes" javaType="Classes">
<result column="cid" property="id" />
<result column="cname" property="cname" />
</association>
</resultMap>
<select id="selectOne" resultMap="STUDENT_CLASSES_MAP">
select s.id,s.sname,s.cid,c.cname from student s left join classes c on s.cid = c.id
where s.id = #{id}
</select>
</mapper>
12.4 多对一(嵌套语句查询)
12.4 多对多
13. 延迟加载
13.1. 概述
- MyBatis中的延迟加载,也称为懒加载,是指在进行关联查询时,按照设置延迟规则推迟对关联对象的select查询,延迟加载可以有效的减少数据库压力
- MyBatis的延迟加载只是对关联对象的查询有延迟设置,对于主加载对象都是直接执行查询语句的
13.2. 开启延迟加载
- mybatis-config.xml
- lazyLoadingEnabled:延迟加载全局开关,设置为true代表开启,当开启时所有对象都会进行延迟加载
- oaggressiveLazyLoading: 此属性表示是否需要按需加载
- true:即便全局延迟加载打开,对象还是会加载所有属性
- false:默认值,所有对象的属性都按需加载
xml
<settings>
<!--开启延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--关闭积极加载-->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
13.3.局部立即加载
- 当我们开启了全局延迟加载之后,如果有些SQL我们需要立即加载,那么可以在collection或者是association标签中指定fetchType属性
- fetchType : 局部指定SQL语句是延迟加载还是立即加载,一般当我们开启了全局延迟加载之后,部分SQL需要立即加载时配置此属性
- eager: 立即加载
- lazy : 延迟加载(默认)
14. Mybatis缓存
1.什么是缓存
- 缓存就是内存中的一个对象,用于对数据库查询结果的保存,用于减少与数据库的互次数从而降低数据库的压力,进而提高响应速度
2.什么是MyBatis缓存
- MvBatis 中的缓存就是说 MvBatis 在执行一次SOL查询之后,这条SOL语句的查询结果并不会消失,而是被MvBatis缓存起来,当再次执行相同SQL语句的时候,就会直接从缓存中进行提取,而不是再次执行SQL命令
- MyBatis中的缓存分为一级缓存和二级缓存
3.MyBatis一级缓存
3.1.概述
- 一级缓存又被称为 SalSession 级别的缓存,是MyBatis默认开启的缓存
- Sglsession是什么: SalSession 是SqlsessionFactory会话工厂创建出来的一个会话的对象,这个SqlSession对象用于执行具体的SQL语句并返回给用户请求的结果
- Salsession级别的缓存是什么意思?SalSession级别的缓存表示的就是每当执行一条SOL语句后,默认就会把该SOL语句缓存起来也被称为会话缓存

3.2.四种失效情况
- 使用不同的SqlSession查询数据
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任意一次增删改操作
- 同一个SqlSession两次查询期间手动清除了缓存
4.MyBatis二级缓存
4.1.概述
二级缓存是SqlsessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取
4.2.二级缓存开启条件
- 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置
- 在映射文件中设置标签
<cache/> - 二级缓存必须在SqlSession关闭或提交之后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口
4.3.失效情况
- 两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
4.4.二级缓存配置
- 在mapper配置文件中添加的
cache标签可以设置一些属性- eviction: 缓存回收策略
- LRU: 最近最少使用的:移除最长时间不被使用的对象,默认的是LRU
- FIFO: 先进先出: 按对象进入缓存的顺序来移除它们
- SOFT: 移除基于垃圾回收器状态和软引用规则的对象
- WEAK: 更积极地移除基于垃圾收集器状态和弱引用规则的对象
- flushlnterval: 刷新间隔,单位毫秒,默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新,也就是执行增刚改才刷新缓存
- size属性: 引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024
- readonly属性:是否只读
- true:只读缓存,会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,性能较高
- false:读写缓存,会返回缓存对象的拷贝,性能较低,但是安全,因此默认是 false
- eviction: 缓存回收策略
4.5.缓存查询执行顺序
- 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭之后,一级缓存中的数据会写入二级缓存