为了减少重复查询给数据库带来的压力,MyBatis提供了缓存机制,这种机制能够缓存查询的结果,避免重复的查询。
MyBatis提供了两种缓存方式:
一种为针对于SqlSession的缓存【默认开启】
另一种为针对于全局的缓存【手动开启】
一级缓存存在SqlSession对象中;二级缓存横跨全部的SqlSession,对所有的查询都生效。
一、一级缓存(本地缓存)
在没有配置的情况下,MyBatis默认开启一级缓存。
在实际开发时,使用同一个SqlSession对象调用同一个Mapper方法,往往只执行一次SQL,这是因为,当开启一级缓存时,第一次查询,MyBatis会将查询结果放在缓存中,当再次使用这个SqlSession进行同一个查询时,如果数据库的数据没有被更改,则直接将缓存中的数据返回,不会再次发送SQL到数据库。
1.用户发送查询请求给MyBatis,MyBatis接收到请求时创建一个SqlSession对象处理本次请求的数据库操作,每个SqlSession对象有对应的执行器,执行器在执行SQL语句时会查询Local Cache中是否存在此查询的缓存,如果不存在,则执行此次查询,并将缓存放到Local Cache中;如果存在,则直接将此次查询的缓存返回。
2.当会话结束,即调用SqlSession的close()方法时,会释放此SqlSession中的所有缓存,并将此SqlSession禁用。如果想要清除缓存中的数据,而不关闭SqlSession对象,可以调用SqlSession的clearCache()方法,此方法会清空该SqlSession一级缓存中的所有内容。除此之外,当SqlSession中执行任何一个DML操作,即增加、删除或更改操作时,都将清空此SqlSession的一级缓存
在MyBatis中,对于两次查询,有以下四个条件来判定它们是否是完全相同的两次查询。 1)传入的statementId是否相同 2)查询时结果集范围是否相同 3)查询的最终SQL语句是否相同 4)传递给Statement的参数是否相同 当这些判断都相同时,认为这两次查询完全相同。
如果想要清除缓存中的数据,而不关闭SqlSession对象,可以调用SqlSession的clearCache()方法,此方法会清空该SqlSession一级缓存中的所有内容。除此之外,当SqlSession中执行任何一个DML操作,即增加、删除或更改操作时,都将清空此SqlSession的一级缓存
演示一级缓存的案例,实现步骤:
1、 添加MyBatis的依赖
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- mybatis的依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<!-- mysql-connector-java 的依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- lombok 的依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<!-- <scope>provided</scope>-->
</dependency>
<!-- junit的依赖 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*</include>
</includes>
</resource>
</resources>
</build>
2、添加MyBatis的核心配置文件
<?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">
<!--MyBatis的核心配置文件-->
<configuration>
<properties resource="jdbc.properties" />
<settings>
<!--开启数据库日志检测-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--类型别名-->
<typeAliases>
<package name="com.ambow.pojo" />
</typeAliases>
<!--配置环境-->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<!--配置映射器-->
<mappers>
<!-- 4.将包内的映射器接口实现全部注册为映射器【推荐】 -->
<package name="com.ambow.dao" />
</mappers>
</configuration>
核心配置文件,需要读取的jdbc.properties文件:
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc
username=root
password=root
3、创建POJO - 基于Lombok
package com.ambow.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Dog {
private int id;
private String name;
private int age;
}
4、创建Mapper接口
//DogMapper.java
package com.ambow.dao;
import com.ambow.pojo.Dog;
public interface DogMapper {
Dog selectDog();
}
5、创建Mapper映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!--DogMapper.xml-->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--MyBatis接口开发,需要修改namespace-->
<mapper namespace="com.ambow.dao.DogMapper">
<select id="selectDog" resultType="dog">
SELECT * from dog where id = 1
</select>
</mapper>
6、创建MybatisUtil工具类
package com.ambow.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.InputStream;
public class MyBatisUtil {
//获取SqlSession
public static SqlSession getSqlSesssion(){
//获取SqlSession
String resource = "mybatis-config.xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
e.printStackTrace();
}
//获取SqlSessionFactory - 工厂对象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// System.out.println(sqlSessionFactory);
//获取SqlSession - 连接对象
SqlSession sqlSession = sqlSessionFactory.openSession();
return sqlSession;
}
//关闭SqlSession
public static void closeSqlSession(SqlSession session){
if (session != null) {
session.close();
}
}
}
7、测试一级缓存
package com.ambow.test;
import com.ambow.dao.DogMapper;
import com.ambow.pojo.Dog;
import com.ambow.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class CacheTest {
/*
一级缓存:
SqlSession级别的缓存,也就是说,同一个SqlSession共用一个缓存对象
*/
@Test
public void test01(){
//获取SqlSession
SqlSession sqlSesssion01 = MyBatisUtil.getSqlSesssion();
//第一次查询 - id为1
DogMapper dogMapper01 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog1 = dogMapper01.selectDog();
System.out.println(dog1);
//第二次查询 - id为1
DogMapper dogMapper02 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog2 = dogMapper02.selectDog();
System.out.println(dog2);
}
/*
一级缓存:
两个SqlSession对象,不会共用一个缓存对象
*/
@Test
public void test02(){
//获取SqlSession
SqlSession sqlSesssion01 = MyBatisUtil.getSqlSesssion();
SqlSession sqlSesssion02 = MyBatisUtil.getSqlSesssion();
//第一次查询 - id为1
DogMapper dogMapper01 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog1 = dogMapper01.selectDog();
System.out.println(dog1);
//第二次查询 - id为1
DogMapper dogMapper02 = sqlSesssion02.getMapper(DogMapper.class);
Dog dog2 = dogMapper02.selectDog();
System.out.println(dog2);
}
/*
一级缓存:
SqlSession调用close()方法,缓存会被释放
*/
@Test
public void test03(){
//获取SqlSession
SqlSession sqlSesssion01 = MyBatisUtil.getSqlSesssion();
//第一次查询 - id为1
DogMapper dogMapper01 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog1 = dogMapper01.selectDog();
System.out.println(dog1);
sqlSesssion01.close();
//第二次查询 - id为1
DogMapper dogMapper02 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog2 = dogMapper02.selectDog();
System.out.println(dog2);
}
/*
一级缓存:
调用SqlSession的clearCache()方法,可以释放缓存
*/
@Test
public void test04(){
//获取SqlSession
SqlSession sqlSesssion01 = MyBatisUtil.getSqlSesssion();
//第一次查询 - id为1
DogMapper dogMapper01 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog1 = dogMapper01.selectDog();
System.out.println(dog1);
//清除缓存
sqlSesssion01.clearCache();
//第二次查询 - id为1
DogMapper dogMapper02 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog2 = dogMapper02.selectDog();
System.out.println(dog2);
}
/*
一级缓存:
当SqlSession中执行任何一个DML操作,即增加、删除或更改操作时,都将清空此SqlSession的一级缓存
*/
@Test
public void test05(){
//获取SqlSession
SqlSession sqlSesssion01 = MyBatisUtil.getSqlSesssion();
//第一次查询 - id为1
DogMapper dogMapper01 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog1 = dogMapper01.selectDog();
System.out.println(dog1);
//执行DML操作 - 数据更新
int row = dogMapper01.updateDog();
System.out.println("执行了更新语句");
//第二次查询 - id为1
DogMapper dogMapper02 = sqlSesssion01.getMapper(DogMapper.class);
Dog dog2 = dogMapper02.selectDog();
System.out.println(dog2);
}
}
二、二级缓存(全局缓存)
MyBatis的二级缓存是Application级别的缓存,与一级缓存的原理类似。
不同的是,二级缓存的作用域扩大到了每个命名空间,在同一个命名空间中的所有查询都将被缓存。
MyBatis二级缓存的执行流程:
1、MyBatis中的二级缓存默认关闭,需要手动开启,当开启后,用户发送有关数据库操作的请求会被CacheExecutor拦截。
2、CacheExecutor拦截数据库操作后,到Configuration对象中查看对应命名空间中的缓存,如果发现存在相同查询的缓存,则直接返回该缓存;如果不存在,则进入一级缓存中查找。
即先经过二级缓存查找后,再从一级缓存中寻找。
MyBatis在执行到DML语句时,会清空当前命名空间中所有的缓存。此外,MyBatis开启二级缓存后可能会有脏读问题:按照开发规范,每个类都有自己的命名空间,命名空间不允许有针对其他类的更改,但如果在B类的命名空间中对A类做出更改时,B类命名空间中的二级缓存将会被清除,A类中的缓存不会被清除,当A类命名空间中有针对于A类的查询操作时,就会寻找二级缓存中的旧数据并将其返回。
演示案例
演示1:不开启二级缓存,一级缓存无法实现跨SqlSession之间的缓存。
演示2:开启二级缓存,可以实现跨SqlSession的缓存。
<!--设置 -->
<settings>
<!--缓存二级配置的全局开关-->
<setting name="cacheEnabled" value="true" />
<!--开启数据库日志检测-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
使用MyBatis的二级缓存,需要以下几步:
-
在主配置文件中开启全局二级缓存配置
<setting name="cacheEnabled" value="true">
-
在映射文件中加入<cache />标签
-
对应的pojo需要实现序列化
-
注意:测试二级缓存需要commit提交,如果不提交是不会保存到二级缓存的
演示3:执行DML操作后,二级缓存会清空。
演示4:在不规范开发时,二级缓存会出现脏读情况。
-
按照开发规范,每个类都有自己的命名空间,命名空间不允许有针对其他类的更改,但如果在B类的命名空间中对A类做出更改时,B类命名空间中的二级缓存将会被清除,A类中的缓存不会被清除,当A类命名空间中有针对于A类的查询操作时,就会寻找二级缓存中的旧数据并将其返回。