1、概述
-
通过使用缓存一些数据(经常使用但是很少更新的),减少对数据库的访问,进而提高系统的效率。
-
MyBatis 等多数持久化框架都提供了缓存策略。
-
MyBatis 的缓存分为一级缓存、二级缓存。
2、一级缓存
-
一级缓存是 MyBatis 默认开启的缓存机制。
-
一级缓存是 SqlSession 级别的缓存,意思是一个
SqlSession
对象会对应有一个缓存空间,同一个SqlSession
对象使用同一个缓存空间。 -
在参数和 SQL 语句完全一样的情况下,我们使用同一个
SqlSession
对象调用一个 Mapper 中的查询方法,只在第一次查询执行一次 SQL,因为使用SqlSession
第一次查询后,MyBatis 会将其放在一级缓存中,以后再查询的时候,如果没有声明需要刷新(没有调用过SqlSession
的clearCache()
、commit()
、close()
等方法),并且缓存没有超时的情况下,SqlSession
都会取当前缓存中的数据,而不会再次发送 SQL 语句到数据库。 -
执行
SqlSession
的C(增加)U(更新)D(删除)操作,或者调
用clearCache()
、commit()
、close()
方法,都会清空一级缓存。 -
注意,必须是由 MyBatis 执行 CUD 操作或者执行当前
SqlSession
对象的clearCache()
、commit()
、close()
方法,才会清空一级缓存;直接对数据库的操作(在数据库命令行客户端等执行 SQL)是无法清空一级缓存的。总之,一级缓存是 MyBatis 提供的,清空必须通过 MyBatis 实现(二级也是这个道理)。 -
在每一次查询后,自动清除一级缓存,在映射配置文件中配置:
<select ····· flushCache="true">
······
</select>
2、二级缓存
-
二级缓存,默认不开启,需要配置。
-
二级缓存,基本上不使用,因为容易造成“脏读”。会有一些更好的技术手段来实现缓存的作用。
-
二级缓存是一种跨 SqlSession 的缓存,是 Namespace 级别或 Mapper 级别的缓存。意思是,即使是不同的
SqlSession
对象,只要使用的是同一个 Mapper,使用的将是同一个二级缓存空间。
![](https://img.haomeiwen.com/i20052341/9b659e109495df02.jpg)
- 二级缓存的清空
![](https://img.haomeiwen.com/i20052341/5ff0d73b96dc14a5.jpg)
- 开启二级缓存:
1、相关的 POJO 需要实现序列化接口,即实现
java.io.Serializable
接口。2、在核心配置文件中配置
<setting name="cacheEnabled" value="true"/>
(默认值为true
,所以不显式配置也行)。cacheEnabled
的含义是“全局性地开启或关闭所有映射器配置文件中已配置的任何缓存”,意思是cacheEnabled
为false
,映射配置文件中的<cache/>
配置也将无效。3、在映射配置文件中配置
<cache/>
。4、在
<select>
标签中,使用useCache=true
,表示当前查询将会使用二级缓存;否则该查询禁用二级缓存。
3、一级缓存与二级缓存的关系
-
二级缓存开启的情况下,查询顺序为:二级缓存 -> 一级缓存 -> 数据库,即二级缓存中没有的到一级缓存中去查,一级缓存中仍没有再到数据库中查询。
-
二级缓存开启的情况下,当数据是从数据库中读取出来的,先放入一级缓存,当执行了
SqlSession
的close()
、commit()
方法后,一级缓存中的内容会存到二级缓存中。
4、案例
4.1、二级缓存造成“脏读”
- 以“用户-订单”为例,“一个用户对应多个订单”。在“查询用户的时候同时查询出所有订单”,对“查询用户”语句使用二级缓存。
先查询出一个用户信息,然后对订单进行更新,然后再次查询该用户信息。由于开启了“用户 Mapper”的二级缓存,而更新是“订单 Mapper”负责,所以“更新订单”的时候是无法清除“用户 Mapper”的二级缓存的,最终,再次查询该用户,仍是从二级缓存中直接获取,而不是从数据库获取。典型“脏读”。
-- 用户表
CREATE TABLE lg_user (
user_id INT,
user_nickname VARCHAR(20) NOT NULL,
user_phone_number VARCHAR(20) NOT NULL,
CONSTRAINT PRIMARY KEY pk_lg_user(user_id)
);
-- 订单表
CREATE TABLE lg_order (
order_id INT,
order_account DECIMAL(8,2) NOT NULL,
order_user_id INT NOT NULL,
CONSTRAINT PRIMARY KEY pk_lg_order(order_id)
);
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
import java.math.BigDecimal;
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString
public class Order implements Serializable {
private static final long serialVersionUID = 7010281991983231524L;
private Integer id;
private BigDecimal account;
}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;
import java.util.List;
@NoArgsConstructor
@AllArgsConstructor
@Data
@ToString()
public class User implements Serializable {
private static final long serialVersionUID = -6402364968509046679L;
private Integer id;
private String nickname;
private String phoneNumber;
private List<Order> orderList;
}
import com.yscyber.mybatis.five.pojo.Order;
import java.util.List;
public interface OrderRepo {
List<Order> listOrdersByUserId(Integer userId);
int insertOrder(Order order);
}
import com.yscyber.mybatis.five.pojo.User;
public interface UserRepo {
User getUserById(Integer id);
}
<?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="com.yscyber.mybatis.five.repo.OrderRepo">
<resultMap id="orderResultMap" type="Order">
<id property="id" column="order_id"/>
<result property="account" column="order_account"/>
</resultMap>
<select id="listOrdersByUserId" parameterType="int" resultMap="orderResultMap">
SELECT order_id, order_account
FROM lg_order
WHERE order_user_id=#{userId}
</select>
<insert id="insertOrder" parameterType="Order">
INSERT INTO lg_order(order_id, order_account, order_user_id)
VALUES (#{id}, #{account}, 1)
</insert>
</mapper>
<?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="com.yscyber.mybatis.five.repo.UserRepo">
<cache/>
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id"/>
<result property="nickname" column="user_nickname"/>
<result property="phoneNumber" column="user_phone_number"/>
<!--
将查询出的 column="user_id" 作为 listOrdersByUserId 参数
-->
<collection property="orderList" ofType="Order" select="com.yscyber.mybatis.five.repo.OrderRepo.listOrdersByUserId" column="user_id">
<id property="id" column="order_id"/>
<result property="account" column="order_account"/>
</collection>
</resultMap>
<select id="getUserById" parameterType="int" resultMap="userResultMap" useCache="true">
SELECT user_id, user_nickname, user_phone_number
FROM lg_user
WHERE user_id=#{id}
</select>
</mapper>
<?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="jdbc.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
<setting name="lazyLoadTriggerMethods" value=""/>
</settings>
<typeAliases>
<package name="com.yscyber.mybatis.five.pojo"/>
</typeAliases>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
<environments default="dev">
<!-- 配置数据源 -->
<environment id="dev">
<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>
<package name="com.yscyber.mybatis.five.repo"/>
</mappers>
</configuration>
@Test
public void test1() throws IOException {
InputStream ris = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ris);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserRepo userRepo = sqlSession.getMapper(UserRepo.class);
OrderRepo orderRepo = sqlSession.getMapper(OrderRepo.class);
User user1 = userRepo.getUserById(1); // UserRepo 查询
System.out.println(user1);
Order order = new Order();
order.setId(4);
order.setAccount(new BigDecimal(23.00));
orderRepo.insertOrder(order); // OrderRepo 更新
sqlSession.commit(); // 这里的 commit 方法无法清空 UserRepo 的二级缓存,只能清空该 SqlSession 对象的一级缓存以及 OrderRepo 的二级缓存(此处 OrderRepo 也没有开启二级缓存)
User user2 = userRepo.getUserById(1);
System.out.println(user2);
sqlSession.close();
}
最后,因为 UserRepo 的二级缓存,读取出的“订单”不是更新后的“订单”,还是第一次查询出的“订单”,造成“脏读”。
网友评论