一、mybatis一级缓存
知识点
- 一级缓存使用场景
- 一级缓存的使用条件
- 一级缓存源码解析
1、一级缓存使用场景
订单表与会员表是存在一对多的关系,为了尽可能减少join查询,进行了分阶段查询,即先查询出订单表,在根据member_id字段查询出会员表,最后进行数据整合。如果订单表中存在重复的member_id,就会出现很多没必要的重复查询。
针对这种情况myBatis通过一缓存来实现,在同一次查询会话中如果出现相同的语句及参数,就会从缓存中取出不在走数据库查询。一级缓存只能作用于查询会话中,所以也叫做会话缓存。
1)数据库设计,新建数据表:t_label
CREATE TABLE `t_label` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO t_label(name)values('青子');
INSERT INTO t_label(name)values('灵棋');
INSERT INTO t_label(name)values('糖糖');
2)新建与数据表对应的pojo:Label
public class Label implements Serializable {
private static final long serialVersionUID = 5965813391610443954L;
private Integer id;
private String name;
//省略setter、getter方法
}
3)创建与之对应的mapper接口
public interface LabelMapper1 {
@Select("select * from t_label where id=#{id}")
Label selectLabelById(Integer id);
@Select("select * from t_label where id=#{id}")
Label getLabelById(Integer id);
@Update("update t_label set name=#{name} where id=#{id}")
Integer updateLabelById(Label label);
}
public interface LabelMapper2 {
@Select("select * from t_label where id=#{id}")
Label selectLabelById(Integer id);
}
4)一级缓存测试类
public class LabelTest {
private SqlSessionFactory sessionFactory1;
private SqlSession session1;
@Before
public void init()throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sessionFactory1 = new SqlSessionFactoryBuilder().build(inputStream);
session1 = sessionFactory1.openSession(true);
}
@Test//同一session同一mapper同一方法相同的sql语句以及参数
public void test01() throws IOException {
LabelMapper1 $proxy = session1.getMapper(LabelMapper1.class);
Label label1 = $proxy.selectLabelById(1);
Label label2 = $proxy.selectLabelById(1);
System.out.println(label1 == label2);
}
@Test//不同session同一mapper同一方法相同的sql语句以及参数
public void test02() throws IOException {
LabelMapper1 $proxy = session1.getMapper(LabelMapper1.class);
Label label1 = $proxy.selectLabelById(1);
SqlSession session2 = sessionFactory1.openSession(true);
Label label2 = session2.getMapper(LabelMapper1.class).selectLabelById(1);
System.out.println(label1 == label2);
}
@Test//同一session不同mapper同一方法相同的sql语句以及相同的参数
public void test03() throws IOException {
LabelMapper1 $proxy = session1.getMapper(LabelMapper1.class);
Label label1 = $proxy.selectLabelById(1);
Label label2 = session1.getMapper(LabelMapper2.class).selectLabelById(1);
System.out.println(label1 == label2);
}
@Test//同一session同一mapper不同方法相同的sql语句以及相同的参数
public void test04() throws IOException {
LabelMapper1 $proxy = session1.getMapper(LabelMapper1.class);
Label label1 = $proxy.selectLabelById(1);
Label label2 = $proxy.getLabelById(1);
System.out.println(label1 == label2);
}
@Test
public void test05() throws IOException {
LabelMapper1 $proxy = session1.getMapper(LabelMapper1.class);
Label label1 = $proxy.selectLabelById(1);
session1.clearCache();//清空以及缓存
Label label2 = $proxy.selectLabelById(1);
System.out.println(label1 == label2);
}
@Test
public void test06() throws IOException {
LabelMapper1 $proxy = session1.getMapper(LabelMapper1.class);
Label label1 = $proxy.selectLabelById(1);
Label label = new Label();
label.setId(20);
label.setName("灵棋");
Integer row = $proxy.updateLabelById(label);
Label label2 = $proxy.selectLabelById(1);
System.out.println(label1 == label2);
}
}
2、一级缓存的使用条件
1)必须是相同的SQL语句和相同的参数
2)必须是相同的会话
3)必须是相同的namespace,即同一个mapper
4)必须是相同的statement,即同一个mapper接口中的同一个方法
5)查询语句中间没有执行session.clearCache()方法
6)查询语句中间没有执行insert、update、delete方法(无论变动记录是否与缓存数据有关系)
3、一级缓存源码解析
一级缓存:基于PerpetualCache的HashMap本地缓存,其存储作用域为Session,当session clearCache或close之后,该session中的所有Cache也将清空
1)缓存的获取
> org.apache.ibatis.session.defaults.DefaultSqlSession#selectList()
> org.apache.ibatis.executor.CachingExecutor#query()
> org.apache.ibatis.executor.BaseExecutor#query()
> org.apache.ibatis.cache.impl.PerpetualCache#getObject()
2)缓存的存储
> org.apache.ibatis.session.defaults.DefaultSqlSession#selectList()
> org.apache.ibatis.executor.CachingExecutor#query()
> org.apache.ibatis.executor.BaseExecutor#query()
> org.apache.ibatis.executor.BaseExecutor#queryFromDatabase()
> org.apache.ibatis.cache.impl.PerpetualCache#putObject()
3)通过对clearCache作为入口追踪到一级缓存的实现PerpetualCache
> org.apache.ibatis.session.defaults.DefaultSqlSession#clearCache()
> org.apache.ibatis.executor.CachingExecutor#clearLocalCache()
> org.apache.ibatis.executor.BaseExecutor#clearLocalCache()
> org.apache.ibatis.cache.impl.PerpetualCache#clear()
二、mybatis二级缓存
知识点:
- 二级缓存使用场景
- CacheNamespace注解或cache标签属性说明
- 二级缓存使用条件
- 二级缓存清除条件
- 二级缓存源码解析
1、二级缓存使用场景
业务系统中存在很多的静态数据,如字典表、菜单表、权限表等,这些数据的特性是不会轻易修改但又是查询的热点数据。一级缓存针对的是同一个会话当中相同SQL,并不适合这情热点数据的缓存场景。为了解决这个问题引入了二级缓存,它脱离于会话之外。
1)数据库设计,新建数据表:t_user
CREATE TABLE `t_user` (
`id` tinyint(4) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`create_time` datetime DEFAULT NULL,
`update_time` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2)新建与数据表对应的pojo:User
public class User implements Serializable {
private static final long serialVersionUID = 6924477354525285670L;
private Integer id;
private String name;
private Date createTime;
private Date updateTime;
//省略setter、getter方法
}
3)创建与之对应的mapper接口
@CacheNamespace(readWrite = true)//深拷贝
public interface UserMapper1 {
@Select("select * from t_user where id = #{id}")
User selectUserById(Integer id);
@Select("select * from t_user where id = #{id}")
User getUserById(Integer id);
@Update("update t_user set name = #{name},update_time = #{updateTime} where id = #{id}")
Integer updateUserById(User user);
Integer modifyUserById(User user);
}
@CacheNamespace()
public interface UserMapper2 {
@Select("select * from t_user where id = #{id}")
User selectUserById(Integer id);
}
4)创建与之对应的mapper.xml文件
<update id="modifyUserById" parameterType="com.cyan.pojo.User">
update t_user set name = #{name},update_time = #{updateTime} where id = #{id}
</update>
5)二级缓存测试类
public class UserTest {
private SqlSessionFactory sessionFactory1;
private SqlSession session1;
@Before
public void init()throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sessionFactory1 = new SqlSessionFactoryBuilder().build(inputStream);
session1 = sessionFactory1.openSession(true);
}
@Test//不同session同一mapper同一方法相同的sql语句以及参数,pojo必须序列化
public void test01() throws IOException {
UserMapper1 $proxy = session1.getMapper(UserMapper1.class);
User user1 = $proxy.selectUserById(1);
session1.close();//会话关闭之后才放入二级缓存中
SqlSession session2 = sessionFactory1.openSession(true);
User user2 = session2.getMapper(UserMapper1.class).selectUserById(1);
System.out.println(user1 == user2);//false深拷贝
}
@Test//不同session不同mapper同一方法相同的sql语句以及参数,pojo必须序列化
public void test02() throws IOException {
UserMapper1 $proxy = session1.getMapper(UserMapper1.class);
User user1 = $proxy.selectUserById(1);
session1.close();
SqlSession session2 = sessionFactory1.openSession(true);
User user2 = session2.getMapper(UserMapper2.class).selectUserById(1);
System.out.println(user1 == user2);
}
@Test//不同session同一mapper不同方法相同的sql语句以及参数,pojo必须序列化
public void test03() throws IOException {
UserMapper1 $proxy = session1.getMapper(UserMapper1.class);
User user1 = $proxy.selectUserById(1);
session1.close();
SqlSession session2 = sessionFactory1.openSession(true);
User user2 = session2.getMapper(UserMapper1.class).getUserById(1);
System.out.println(user1 == user2);
}
@Test
public void test04() throws IOException {
UserMapper1 $proxy = session1.getMapper(UserMapper1.class);
User user1 = $proxy.selectUserById(1);
session1.close();
User user = new User();
user.setId(2);
user.setName("灵棋");
user.setUpdateTime(new Date());
SqlSession session2 = sessionFactory1.openSession(true);
session2.getMapper(UserMapper1.class).updateUserById(user);
session2.close();
SqlSession session3 = sessionFactory1.openSession(true);
User user2 = session3.getMapper(UserMapper1.class).selectUserById(1);
System.out.println(user1 == user2);
}
@Test
public void test05() throws IOException {
UserMapper1 $proxy = session1.getMapper(UserMapper1.class);
User user1 = $proxy.selectUserById(1);
session1.close();
User user = new User();
user.setId(2);
user.setName("灵棋");
user.setUpdateTime(new Date());
SqlSession session2 = sessionFactory1.openSession(true);
session2.getMapper(UserMapper1.class).modifyUserById(user);
session2.close();
SqlSession session3 = sessionFactory1.openSession(true);
User user2 = session3.getMapper(UserMapper1.class).selectUserById(1);
System.out.println(user1 == user2);
}
}
2、CacheNamespace注解或cache标签属性说明
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface CacheNamespace {
//缓存Cache的实现类
Class<? extends Cache> implementation() default PerpetualCache.class;
//缓存算法
Class<? extends Cache> eviction() default LruCache.class;
//刷新间隔时间,单位毫秒
long flushInterval() default 3600000L;
//最大缓存引用对象
int size() default 1000;
//是否可写
boolean readWrite() default true;
}
3、二级缓存使用条件
1.当会话提交或关闭之后才会填充二级缓存
2.必须是在同一个命名空间之下
3.必须是相同的namespace即同一个mapper
4.必须是相同的statement即同一个mapper接口中的同一个方法
5.必须是相同的SQL语句和参数
6.如果readWrite=true,实体对像必须实现Serializable接口
4、二级缓存清除条件
1.xml中配置的update不能清空@CacheNamespace 中的缓存数据(非本cacheNamespace的update操作不能清空本cacheNamespace中的缓存数据)
2.只有修改会话提交之后才会执行清空操作
3.任何一种增删改操作都会清空整个namespace 中的缓存
5、二级缓存源码解析
二级缓存与一级缓存机制相同,默认也是采用PerpetualCache,本地HashMap存储,不同在于其存储作用域为Mapper(Namespace)
1)清除缓存
xml中配置的update不能清空@CacheNamespace 中的缓存数据
只有修改会话提交之后,才会执行清空操作
任何一种增删改操作,都会清空整个namespace中的缓存
2)缓存源码解析
清除缓存
> org.apache.ibatis.session.defaults.DefaultSqlSession#selectList()
> org.apache.ibatis.executor.CachingExecutor#query()
> org.apache.ibatis.executor.CachingExecutor#query()
> org.apache.ibatis.executor.CachingExecutor#flushCacheIfRequired()
获取缓存关键源码(装饰者设计模式:职责分离原则)
> org.apache.ibatis.cache.TransactionalCacheManager#getObject()
> org.apache.ibatis.cache.decorators.TransactionalCache#getObject()
> org.apache.ibatis.cache.decorators.SynchronizedCache#getObject()
> org.apache.ibatis.cache.decorators.LoggingCache#getObject()
> org.apache.ibatis.cache.decorators.SerializedCache#getObject()
> org.apache.ibatis.cache.decorators.ScheduledCache#getObject()
> org.apache.ibatis.cache.decorators.LruCache#getObject()
> org.apache.ibatis.cache.impl.PerpetualCache#getObject()
保存二级缓存
> org.apache.ibatis.executor.CachingExecutor#close()
> org.apache.ibatis.cache.TransactionalCacheManager#commit()
> org.apache.ibatis.cache.decorators.TransactionalCache#flushPendingEntries()
> org.apache.ibatis.cache.decorators.SynchronizedCache#putObject()
> org.apache.ibatis.cache.decorators.LoggingCache#putObject()
> org.apache.ibatis.cache.decorators.SerializedCache#putObject()
> org.apache.ibatis.cache.decorators.ScheduledCache#putObject()
> org.apache.ibatis.cache.decorators.LruCache#putObject()
> org.apache.ibatis.cache.impl.PerpetualCache#putObject()