目录
概述
MyBatis的环境搭建
测试
API接口说明
面向接口开发需要遵循的规范
Mybatis日志
参数传递
结果处理
对象映射
#{}和${}区别
特殊处理使用resultMap
定义resultMap
使用resultMap
注意事项
多表关联处理结果集
嵌套查询
注解方式
常用注解标签
使用案例
Mybatis动态SQL
if元素
where元素
编辑
trim元素
choose元素
set元素
set元素实现代码
trim元素实现代码
foreach元素
特殊符号处理
MyBatis的一级缓存和二级缓存
为什么使用缓存
一级缓存
一级缓存工作模式
编辑
一级缓存演示代码
二级缓存
二级缓存配置操作
二级缓存工作模式
二级缓存演示代码
MyBatis架构
概述
MyBatis是一款优秀的半自动的ORM持久层框架,它支持自定义SQL,存储过程以及高级映射
MyBatis支持几乎所有的JDBC代码以及设置参数和获取结果集的工作
MyBatis将基本的JDBC常用接口封装,对外提供操作即可。
MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis是一款优秀的持久层框架。
那么什么是持久层呢?
持久层:可以立即保存在磁盘上
什么是ORM?
ORM(Object Relation Mapping),对象关系映射。对象指的是Java对象,关系指的是数据库中的关系模型,对象关系映射,指的就是在Java对象和数据库的关系模型之间建立一种对应的关系。例如,Java中的一个Student类,来对应数据库中的一张Student表,类中的属性和表中的列一一对应。Student类就对应Student表,一个Student对象就对应student表中的一行数据。
MyBatis的环境搭建
第一步.创建一张表和表对应的实体类
这里我创建了一个Student类和student表来举例
package com.ffyc.mybatis.model;
import java.io.Serializable;
public class Student implements Serializable {
private Integer id;
private Integer num;//建议不使用基本类型 建议使用包装类型 默认值是null
private String name;
private String gender;
private Major major;//类与类之间的关联关系 has-a 什么有什么
public Major getMajor() {
return major;
}
public void setMajor(Major major) {
this.major = major;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getNum() {
return num;
}
public void setNum(Integer num) {
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", num=" + num +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", major=" + major +
'}';
}
}
第二步.导入MyBatis jar包和mysql数据驱动包
<!--mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
这里建议大家可以将这些常用的jar包整理起来,方便以后得使用。
第三步.创建MyBatis全局配置文件
需要将其放在一个名为mybatis.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"><!--dtd文件是用来制定xml标签规范的-->
<configuration>
<environments default="development">
<environment id="development">
<!--
事务管理方式:就是一次对数据库操作过程中,执行多条sql管理
把所有的操作都成功执行后,再提交事务,让数据库最终执行本次提交的所有sql
-->
<!--事务管理方式-->
<transactionManager type="JDBC"/><!--使用jdbc事务管理-->
<!--数据库连接池配置
频繁地创建销毁与数据库的连接对象是比较占用时间和空间
可以在池子中默认创建若干个连接对象,有请求使用时,直接从连接池中取出一个对象,用完再还回去,
减少了创建和销毁的时间开销
-->
<dataSource type="POOLED">
<!--数据库连接-->
<property name="driver" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://127.0.0.1:3306/ssmdb?serverTimezone=Asia/Shanghai" />
<property name="username" value="root" />
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
</configuration>
第四步.创建sql映射文件
sql映射文件放在resources目录下创建一个mapper目录 创建一个StudentMapper.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"><!--dtd文件 叫xml的约束文件,定义xml标签-->
<mapper namespace="定义接口地址">
sql语句
</mapper>
在我们搭建好MyBatis环境后,我们需要测试一下。
定义一个接口StudentDao
package com.ffyc.mybatis.dao;
import com.ffyc.mybatis.model.Student;
import java.util.List;
public interface StudentDao {
List<Student> findStudents(Student student);
}
测试
我们可以查询一下我们在数据库中存储的学生信息
可以使用以下的代码来测试一下
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.AdminDao;
import com.ffyc.mybatis.model.Admin;
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 javax.xml.ws.soap.Addressing;
import java.io.IOException;
import java.io.Reader;
import java.util.List;
public class Test1 {
public static void main(String[] args) throws IOException {
//读入mybatis核心配置文件
Reader reader = Resources.getResourceAsReader("mybatis.xml");
//创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
//创建SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
//为接口创建一个代理对象
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
//调用我们自己的方法
List<Student> studentList = studentDao.findStudents();
System.out.println(studentList);
sqlSession.commit();//提交数据库事务 事务只针对新增 修改 删除操作 查询不需要提交事务的
//关闭与数据库的会话对象
sqlSession.close();
}
}
读取MyBatis核心配置文件
Reader reader = Resources.getResourceAsReader("mybatis.xml");创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
创建SqlSession对象
SqlSession sqlSession =sqlSessionFactory.openSession();
为接口创建一个代理对象
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
API接口说明
SqlSessionFactory 接口
使用 SqlSessionFactory 来创建 SqlSession,一旦创建 SqlSessionFactory就会在整个应用过程中始终存在。由于创建开销较大,所以没有理由去销毁再创建它,一个应用运行中也不建议多次创建 SqlSessionFactory。
SqlSession 接口
Sqlsession 意味着创建与数据库链接会话,该接口中封装了对数据库操作的方法,与数据库会话完成后关闭会话。
由于SqlSessionFactory接口在整个过程中只会创建一次,所以为了简化我们在项目中的代码,我们可以把其创建过程放在一个静态代码块里。
具体的代码如下 我们可以创建一个MybatisUtil的Class文件
package com.ffyc.mybatis.util;
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.Reader;
public class MybatisUtil {
static SqlSessionFactory sqlSessionFactory = null;
static {
//读入mybatis核心配置文件
Reader reader = null;
try {
reader = Resources.getResourceAsReader("mybatis.xml");
} catch (IOException e) {
e.printStackTrace();
}
/*
创建SqlSessionFactory对象
SqlSessionFactory对象是用来创建SqlSession对象的
由于SqlSessionFactory对象创建的开销较大,所以一个项目中只创建一个SqlSessionFactory对象,不用关闭
*/
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
在以后得测试代码中,我们就可以向以下的代码这样写
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.StudentDao;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class Test3 {
public static void main(String[] args) {
/*
SqlSession对象是与数据库交互的,每次与数据库连接都需要创建一个新的连接对象
用完关闭即可
*/
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
/*Student student = studentDao.findStudentById(1);
System.out.println(student);*/
/* List<Student> students = studentDao.findStudents();
System.out.println(students);*/
/* Student student = studentDao.findStudentById1(1);
System.out.println(student);*/
sqlSession.commit();
sqlSession.close();
}
}
这样的代码会更加简洁一些。
面向接口开发需要遵循的规范
1、 Mapper.xml 文件中的 namespace 与 mapper 接口的类路径相同。
2、 Mapper 接口方法名和 Mapper.xml 中定义的每个 statement 的 id 相同。
3、 Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的
parameterType 的类型相同。
4、 Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的
resultType 的类型相同。
Mybatis日志
具体选择哪个日志实现由 MyBatis 的内置日志工厂确定。它会使用最先找到的。
Mybatis 内置的日志工厂提供日志功能,具体的日志实现有以下几种方式:
SLF4J|LOG4J|JDK_LOGGINGCOMMONS_LOGGING|STDOUT_LOGGING
配置方法
这个需要放在我们创建的StudentMapper文件中。
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/><!--打印执行日志-->
</settings>
参数传递
参数传递分为单个参数直接传递和多个参数使用@Param("id")绑定
单个参数直接传递
Admin findAdminById(int id);
多个参数使用@Param("id")绑定
Admin login(@Param("account") String account,@Param("password") String password);
具体代码如下
单个参数直接传递
<select id="findAdminById" parameterType="int" resultType="Admin">
select * from admin where id = #{id}
</select>
多个参数使用@Param("id")绑定
<select id="login" resultType="Admin">
select * from admin where account = #{account} and password = #{password}
</select>
如果传入一个复杂的对象,就需要使用paramterType参数进行类型定义:
例如:
void updateAdmin(Admin admin);
结果处理
对象映射
#{}和${}区别
#{}占位符,是经过预编译的,编译好SQL语句在取值,#方式能够防止sql注入
#{}:delete from admin where id = #{id}
结果:deletee from admin where id = ?
${}会将值以字符串形式拼接到sql语句,${}方式无法防止sql注入
${}:delete from admin where id = '${id}'
结果:delete from admin where id = '1'
一般是#{}向sql传值使用,而使用${}向sql传列名
例如在 order by ${column} 语句后面可以动态替换列名
特殊处理使用resultMap
定义resultMap
<resultMap id="adminmap" type="Admin">
<id property="id" column="id"></id><!--映射主键的-->
<result property="account" column="account"></result>
<result property="password" column="password"></result>
<result property="gender" column="gender"></result>
<result property="adminAge" column="admin_age"></result>
</resultMap>
使用resultMap
<select id="findAdminByAccount" parameterType="string" resultMap="adminmap">
select * from admin where account = #{account}
</select>
注意事项
1.resultMap的id属性是resultMap的唯一标识,本例中定义为"adminmap"
2.resultMap的type属性是映射的POJO类型
3.id标签映射主键,result标签映射非主键
4.property设置对象的属性名称,column映射查询结果的列名称
多表关联处理结果集
此处需要用到resultMap元素中association,collection元素
Collection元素处理一对多关联
这里我用学生和专业的关系来举例
专业与学生一对多关系
专业一方,配置多方集合
public class Major {
private Integer id;
private String name;
private List<Student> students;
}
学生多方,在多方配置一方
public class Student implements Serializable {
private Integer id;
private Integer num;//建议不使用基本类型 建议使用包装类型 默认值是null
private String name;
private String gender;
private Major major;//类与类之间的关联关系 has-a 什么有什么
}
使用resultMap组装查询结果
<!--自定义结果映射关系-->
<resultMap id="studentMap" type="Student"><!--最终返回一个学生对象 将学生信息封装学生对象-->
<id property="id" column="id"></id><!--映射主键的-->
<result property="num" column="num"></result>
<result property="name" column="name"></result>
<result property="gender" column="gender"></result>
<association property="major" javaType="Major">
<result property="name" column="mname"></result>
</association>
</resultMap>
<select id="findStudentById" parameterType="int" resultMap="studentMap">
SELECT
s.id,
s.num,
s.name,
s.gender,
m.name mname
FROM
student s LEFT JOIN major m ON s.majorid = m.id
WHERE
s.id = #{id}
</select>
嵌套查询
将一个多表关联查询拆分为多次查询 先查询主表数据,然后查询关联表数据。
<!--嵌套查询 把一个关联查询分成两个单表查询,然后通过学生专业外键,查询关联专业信息-->
<resultMap id="studentMap1" type="Student">
<association property="major" javaType="Major" select="findMajorById" column="majorid"></association>
</resultMap>
<select id="findStudentById1" parameterType="int" resultMap="studentMap1">
SELECT
s.id,
s.num,
s.name,
s.gender,
s.majorid
FROM
student s
WHERE
s.id = #{id}
</select>
<select id="findMajorById" parameterType="int" resultType="Major">
SELECT
*
FROM
major
WHERE
id = #{id}
</select>
注解方式
常用注解标签
@Insert : 插入 sql , 和 xml insert sql 语法完全一样
@Select : 查询 sql, 和 xml select sql 语法完全一样
@Update : 更新 sql, 和 xml update sql 语法完全一样
@Delete : 删除 sql, 和 xml delete sql 语法完全一样
@Param : 入参
@Results : 设置结果集合
@Result : 结果
使用案例
Mybatis动态SQL
可以根据具体的参数条件,来对SQL语句进行动态拼接。
if元素
if标签可以对传入的条件进行判断
<if test="name!=null">
name = #{name},
</if>
<if test="num!=null">
num = #{num},
</if>
<if test="gender!=null">
gender = #{gender}
</if>
where元素
where元素会进行判断,如果他包含的标签中有返回值的话,它就插入一个'where'
此外,如果标签返回的内容是以AND 或 OR开头,它会剔除掉AND 或 OR
<select id="findStudents" resultType="Student">
select * from student
<where>
<if test="name!=null">
name = #{name}
</if>
<if test="num!=0">
and num = #{num}
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
</where>
</select>
我们可以测试一下 看看运行时候的sql语句
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.StudentDao;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
import java.util.List;
public class Test5 {
public static void main(String[] args) {
/*
SqlSession对象是与数据库交互的,每次与数据库连接都需要创建一个新的连接对象
用完关闭即可
*/
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setName("张泽兴");
student.setGender("男");
student.setNum(20221808);
List<Student> students = studentDao.findStudents(student);
System.out.println(students);
sqlSession.commit();
sqlSession.close();
}
}
我们可以看到运行时的sql语句为
trim元素
让我们添加一个指定的前缀关键字,让我们去除指定的关键字
<!--trim 让我们添加一个指定的前缀关键字
让我们去除指定的关键字-->
<select id="findStudents" resultType="Student">
select * from student
<trim prefix="where" prefixOverrides="and|or">
<if test="name!=null">
name = #{name}
</if>
<if test="num!=0">
and num = #{num}
</if>
<if test="gender!=null">
and gender = #{gender}
</if>
</trim>
</select>
测试代码
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.StudentDao;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
import java.util.List;
public class Test5 {
public static void main(String[] args) {
/*
SqlSession对象是与数据库交互的,每次与数据库连接都需要创建一个新的连接对象
用完关闭即可
*/
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setName("张泽兴");
student.setGender("男");
student.setNum(20221808);
List<Student> students = studentDao.findStudents(student);
System.out.println(students);
sqlSession.commit();
sqlSession.close();
}
}
sql语句
choose元素
有时候我们不想使用所有的条件,而只是想从多个条件中选择一个使用。
choose元素有点像我们在JavaSE中学到的switch语句
<select id="findStudents" resultType="Student">
select * from student
<where>
<choose>
<when test="name!=null">
name = #{name}
</when>
<when test="num!=0">
and num = #{num}
</when>
<otherwise>
name = '李钰轩'
</otherwise>
</choose>
</where>
</select>
测试代码:
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.StudentDao;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
import java.util.List;
public class Test5 {
public static void main(String[] args) {
/*
SqlSession对象是与数据库交互的,每次与数据库连接都需要创建一个新的连接对象
用完关闭即可
*/
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setNum(20221807);
List<Student> students = studentDao.findStudents(student);
System.out.println(students);
sqlSession.commit();
sqlSession.close();
}
}
这里我们传入的student对象里有学生的学号,所以我们查询的结果就是为此学号的学生信息
如果我们传入的student对象里的学生学号或者学生姓名为null 这时就会查询<otherwise></otherwise> 里设置的条件
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.StudentDao;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
import java.util.List;
public class Test5 {
public static void main(String[] args) {
/*
SqlSession对象是与数据库交互的,每次与数据库连接都需要创建一个新的连接对象
用完关闭即可
*/
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setNum(0);
student.setName(null);
List<Student> students = studentDao.findStudents(student);
System.out.println(students);
sqlSession.commit();
sqlSession.close();
}
}
以下为运行结果:
set元素
set元素可以把最后一个逗号去掉
这个功能trim元素也可以实现
set元素实现代码
<!--
<set> 动态添加set关键字 还可以去掉最后的逗号
-->
<update id="updateStudent" parameterType="student">
update student
<set>
<if test="name!=null">
name = #{name},
</if>
<if test="num!=null">
num = #{num},
</if>
<if test="gender!=null">
gender = #{gender}
</if>
</set>
where id = #{id}
</update>
测试代码
<update id="updateStudent" parameterType="student">
update student
<trim prefix="set" suffixOverrides=",">
<if test="name!=null">
name = #{name},
</if>
<if test="num!=null">
num = #{num},
</if>
<if test="gender!=null">
gender = #{gender}
</if>
</trim>
where id = #{id}
</update>
sql语言
trim元素实现代码
<update id="updateStudent" parameterType="student">
update student
<trim prefix="set" suffixOverrides=",">
<if test="name!=null">
name = #{name},
</if>
<if test="num!=null">
num = #{num},
</if>
<if test="gender!=null">
gender = #{gender}
</if>
</trim>
where id = #{id}
</update>
测试代码
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.StudentDao;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
import java.util.List;
public class Test5 {
public static void main(String[] args) {
/*
SqlSession对象是与数据库交互的,每次与数据库连接都需要创建一个新的连接对象
用完关闭即可
*/
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
Student student = new Student();
student.setId(3);
student.setName("李四");
sqlSession.commit();
sqlSession.close();
}
}
sql语言
foreach元素
主要用在构建 in 条件中,它可以在 SQL 语句中进行迭代一个集合。foreach
元素的属性主要有 item,index,collection,open,separator,close。
item 表示集合中每一个元素进行迭代时的别名,index 指定一个名字,用于
表示在迭代过程中,每次迭代到的位置,open 表示该语句以什么开始,
separator 表示在每次进行迭代之间以什么符号作为分隔符,close 表示以什
么结束,在使用 foreach 的时候最关键的也是最容易出错的就是 collection
属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的。
– 如果传入的是单参数且参数类型是一个 List 的时候,collection 属 性值为 list
– 如果传入的是单参数且参数类型是一个 array 数组的时候,collection 的属性值为 array
测试代码
<select id="findStudentsByColumn" resultType="Student">
select
<foreach collection="list" item="c" separator=",">
${c}
</foreach>
from student
</select>
特殊符号处理
在 mybatis 中的 xml 文件中,存在一些特殊的符号,比如:<、>、"、&、<>等,正常书写 mybatis 会报错,需要对这些符号进行转义。具体转义如下所示:
特殊字符 转义字符
< <
> >
"
"
’ '
&
&
除了可以使用上述转义字符外,还可以使用<![CDATA[]]>来包裹特殊字符。如
下所示:
<if test="id != null">
AND <![CDATA[ id <> #{id} ]]>
</if>
<![CDATA[
]]>是 XML 语法。在 CDATA 内部的所有内容都会被解析器忽略。
但是有个问题那就是 <if> </if> <where> </where>
<choose> </choose> <trim> </trim> 等这些标签都不会被解析,所以
我们只把有特殊字符的语句放在 <![CDATA[
]]> 尽量缩小<![CDATA[ ]]>
的范围
演示代码
<!--
xml 中特殊符号的处理
解决方式1:使用转义字符代替
解决方式2:<![CDATA[ 特殊符号]]>
-->
<select id="findStudents1" parameterType="int" resultType="Student">
select * from student where num <![CDATA[<]]> #{num}
</select>
MyBatis的一级缓存和二级缓存
为什么使用缓存
缓存的作用是为了减轻数据库的压力,提高查询性能。缓存实现的原理是从数据库中查询出来的对象在使用完后不要销毁,而是存储在内存(缓存)中,当再次需要获取该对象时,直接从内存(缓存)中直接获取,不再向数据库 执行 select 语句,从而减少了对数据库的查询次数,因此提高了数据库的性能。
Mybatis 有一级缓存和二级缓存。一级缓存的作用域是同一个 SqlSession,在同一个 sqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。当一个 sqlSession 结束后该 sqlSession 中的一级缓存也就不存在了。Mybatis 默认开启一级缓存。
二级缓存是多个 SqlSession 共享的,其作用域是同一个 namespace,不同的 sqlSession 两次执行相同 namespace 下的 sql 语句且向 sql 中传递参数也相同即最终执行相同的 sql 语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率,Mybatis 默认没有开启二级缓存需要在 setting 全局参数中配置开启二级缓存。
一级缓存
Mybatis 对缓存提供支持,但是在没有配置的默认情况下,它只开启一级缓存,一级缓存只是相对于同一个 SqlSession 而言。所以在参数和 SQL 完全一样的情况下,我们使用同一个 SqlSession 对象调用一个 Mapper 方法,往往只执行一次 SQL,因为使用 SelSession 第一次查询后MyBatis 会将其放在缓存中,以后再查询的时候,如果没有声明需要刷新,并且缓存没有超时的情况下,SqlSession 都会取出当前缓存的数据,而不会再次发送 SQL 到数据库。
一级缓存工作模式
一级缓存演示代码
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.StudentDao;
import com.ffyc.mybatis.model.Student;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import java.util.ArrayList;
public class Test6 {
public static void main(String[] args) {
/*
mybatis 框架也是提供了缓存功能
一级缓存:是SqlSession级别的,在同一个Sqlsession中相同的两次查询,只查询一次数据库
将第一次查询的数据存储在SqlSession对象中,第二次查询直接获取即可
一级缓存失效
1.执行增删改操作后,当前的一级缓存会清空
2.sqlSession.clearCache();强制清空一级缓存
3.sqlSession.close(); 关闭连接对象,清空一级缓存
*/
/*
SqlSession对象是与数据库交互的,每次与数据库连接都需要创建一个新的连接对象
用完关闭即可
*/
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
studentDao.findStudents1(1);//在同一个sqlSession中,再次查询相同的内容,会从一级缓存中查询,不会查询数据库
//执行删除操作,修改 新增操作后 会清空以及缓存数据
//sqlSession.clearCache();//强制清空一级缓存
studentDao.findStudents1(1);
sqlSession.commit();
sqlSession.close();
}
}
我们执行两次一样的查询操作,会发现在最后的输出结果中显示只会进行一次数据库的连接操作,因为以及缓存会将第一次查询的数据存储在SqlSession对象中,第二次查询直接获取即可。
但是如果我们在两次查询操作的中间加上
sqlSession.clearCache()(强制清空一级缓存),这时我们就会发现他会进行两次数据库的连接操作。
二级缓存
二级缓存是 SqlSessionFactory 级别的,根据 mapper 的 namespace 划分区域的,相同 namespace 的 mapper 查询的数据缓存在同一个区域,如果使用 mapper 代理方法每个 mapper 的 namespace 都不同,此时可以理解为二级缓存区域是根据 mapper 划分。 每次查询会先从缓存区域查找,如果找不到则从数据库查询,并将查询到数据写入缓存。Mybatis 内部存储缓存使用一个 HashMap,key 为hashCode+sqlId+Sql 语句。value 为从查询出来映射生成的 java 对象。sqlSession 执行 insert、update、delete 等操作 commit 提交后会清空缓存区域,防止脏读。
二级缓存配置操作
第一步.启用二级缓存
在我们前面创建的mybatis.xml文件中加入
<!--启用二级缓存-->
<setting name="cacheEnabled" value="true"/>
cacheEnabled 设置为 true 时启用二级缓存,设置为 false 时禁用二级缓存。
第二步.对象序列化
在我们创建时实体类对象实现序列化接口java.io.Serialzable
public class Student implements Serializable {
}
第三步.配置映射文件
在我们定义的StudentMapper.xml映射文件中添加<cache />
<!--设置二级缓存配置的
size="20" 缓存对象数量
flushInterval=""设置二级缓存的有效时间
-->
<cache size="20" flushInterval="3000"></cache>
二级缓存工作模式
二级缓存演示代码
package com.ffyc.mybatis.test;
import com.ffyc.mybatis.dao.StudentDao;
import com.ffyc.mybatis.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
public class Test7 {
public static void main(String[] args) {
/*
mybatis 框架也是提供了缓存功能
一级缓存:是SqlSession级别的,在同一个Sqlsession中相同的两次查询,只查询一次数据库
将第一次查询的数据存储在SqlSession对象中,第二次查询直接获取即可
一级缓存失效
1.执行增删改操作后,当前的一级缓存会清空
2.sqlSession.clearCache();强制清空一级缓存
3.sqlSession.close(); 关闭连接对象,清空一级缓存
二级缓存:
二次缓存是sqlSessionFactory级别(sqlSessionFactory对象只有一个,创建后就不关闭了,多个Sqlsession共享一个sqlSessionFactory)
使用是需要配置的
1.启用二级缓存
<setting name="cacheEnabled" value="true"/>
2.对象序列化
将所有的 POJO 类实现序列化接口 Java.io. Serializable
3.配置映射文件
在 Mapper 映射文件中添加<cache />,表示此mapper 开启二级缓存
*/
/*
SqlSession对象是与数据库交互的,每次与数据库连接都需要创建一个新的连接对象
用完关闭即可
*/
SqlSession sqlSession = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao = sqlSession.getMapper(StudentDao.class);
studentDao.findStudents1(2);
sqlSession.commit();
sqlSession.close();
SqlSession sqlSession1 = MybatisUtil.getSqlSession();
//获取接口的代理对象,由代理对象调用接口中对应方法所匹配的sql
StudentDao studentDao1 = sqlSession1.getMapper(StudentDao.class);
studentDao1.findStudents1(2);
sqlSession1.commit();
sqlSession1.close();
}
}
因为二级缓存是SqlSessionFactory 级别的,SqlSessionFactory对象只有一个,这里我们创建两个sqlsession对象,用来进行同样的查询操作,会发现在结果中1只进行了一次数据库的连接操作。