认识Mybatis的一二级缓存


一次完整的数据库请求,首先根据配置文件生成SqlSessionFactory,再通过SqlSessionFactory开启一次SqlSession,在每一个SqlSession中维护着一个Executor实例,通过Executor实例,可以获取到Statement然后结合输入的参数,

查询结果集,Mybatis的一级缓存是在发生executor阶段,在executor内部维护着一个PerpetualCache实例完成缓存,PerpetualCache由一个id和HashMap组成,一级缓存和Sqlsession实例进行绑定的,每一次sqlSession都有一个

PerpetualCache进行缓存,不同的sqlSession之间不会相互影响,所以当SqlSession关闭或者commit的时候,一级缓存就会消失。二级缓存是mapper级别的缓存,二级缓存和namespace进行绑定,多个SqlSession去操作同一个Mapper的sql语句,

多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的,二级缓存也是作用于Executor实例之中,由CachingExecutor对Executor的实现类BaseExecutor类进行增强,从而增加了缓存的功能,二级缓存需要将查询结果序列化,

所以用来接收结果的pojo需要实现Serializable接口。

一、一级缓存

可以通过代码来认识一级缓存,需要准备以下jar包:

(1)Mybatis的核心包

(2) log4j日志包

搭建一个简单的demo:

自行创建一个user表,字段自定,mybatisConfig.xml,Mapper.xml, Mapper.java, ServiceExecutor.java内容如下:

mybatisConfig.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>
<settings>
<setting name="logImpl" value="LOG4J"/>
<!-- <setting name="cacheEnabled" value="true" /> -->
</settings> <environments default="development">
<environment id="development">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/goods"/>
<property name="username" value="root"/>
<property name="password" value="www1928..com"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/idt/mybatis/mapper/UserMapper.xml"/>
</mappers>
</configuration>

log4j.properties

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# MyBatis logging configuration...
log4j.logger.com.idt.mybatis=DEBUG
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

Mapper.xml

package com.idt.mybatis.mapper;

import java.util.List;
import java.util.Map; public interface UserMapper {
public List<Map<String, String>> getUserList();
}

ServiceExecutor.java

package com.idt.mybatis.service;

import java.util.List;
import java.util.Map; import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder; import com.idt.mybatis.mapper.UserMapper; public class ServiceExecutor { private static SqlSessionFactory sqlSessionFactory;
static {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(ServiceExecutor.class.getClassLoader().getResourceAsStream("mybatisConfig.xml"));
}
/*
* 一级缓存验证
*/
public void queryUserList() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
/*第一次查询*/
List<Map<String, String>> userList1 = userMapper.getUserList();
//清除缓存
//sqlSession.clearCache();
/*第二次查询*/
List<Map<String, String>> userList2 = userMapper.getUserList();
sqlSession.close();
} public static void main(String[] args) {
ServiceExecutor executor = new ServiceExecutor();
executor.queryUserList();
}
}

执行查询,打印控制台的日志如下:

可以看到代码中的两次查询却只执行了一次,所以在sqlSession没有关闭的情况下,第二次查询到的是缓存里的数据,通过sqlSession.clearCache()方法我们清空缓存然后再执行:

清空缓存之后,可以发现两次查询都取访问了数据库,前面已经说过,一级缓存只作用在sqlSession级别,所以当sqlSession关闭或者提交之后都会清空缓存。

源码中如下

在SSM环境中,Mybatis的SqlSessionFactory交由Spring维护,在每次Sqlsession执行查询之后都是自动close,所以在SSM环境,一级缓存是失效的。

二、二级缓存

由于一级缓存适用环境少,所以为了Mybatis为了提供能适用更多场景的二级缓存,二级缓存不再局限于SqlSession级别,而是作用于Mapper(namespace)级别,或者说在SqlSessionFactory级别

在原来的demo做以下添加:

在mybatisConfig.xml添加

<setting name="cacheEnabled" value="true" />

在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="com.idt.mybatis.mapper.UserMapper">
<cache>
</cache>
<select id="getUserList" resultType="map">
select * from t_user
</select>
<select id="getUserByname" resultType="map">
select * from t_user where loginname = #{loginname}
</select>
</mapper>

在Mapper.java新增接口

public Map<String, String> getUserByname(@Param("loginname") String name);

在ServiceExecutor.java中添加方法:

   /*
* 二级缓存验证
*/
public void queryUserByname() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
Map<String, String> user1 = userMapper1.getUserByname("liSi");
System.out.println("第一次查询:" + user1.toString());
sqlSession1.close();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
Map<String, String> user2 = userMapper2.getUserByname("liSi");
System.out.println("第二次查询:" + user2.toString());
sqlSession1.close();
}

执行该方法:

可以看出第二次的数据来自于缓存,两次虽然是不同的sqlsession但是有着相同的sqlSessionFactory,查询sql以及相同的查询参数,mybatis会根据这个进行判断是否两次查询一样,来减少数据的连接然后提高查询效率,

但是问题也随之而来,如果在两次查询之间添加一个修改操作,那么mybatis还会用缓存之中的数据吗?前面提高过,二级缓存是作用于namespace级别,也就是在一个namespace中进行update,del, insert等操作都是清空缓存,

在之前的源码截图中可以看到,所以我们要验证不再一个namespace下,会不会因为出现缓存出现脏读,将demo做如下修改:

增加新的mapper映射UserMapper2,

UserMapper2.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="com.idt.mybatis.mapper.UserMapper">
<delete id="updateUser" >
update t_user set loginname=#{newname} where loginname = #{loginname}
</delete>
</mapper>

UserMapper.java

package com.idt.mybatis.mapper;

import org.apache.ibatis.annotations.Param;

public interface UserMapper2 {
public int updateUser(@Param("newname") String newname, @Param("loginname") String name);
}

mybatisConfig.xml

<mapper resource="com/idt/mybatis/mapper/UserMapper2.xml"/>

ServiceExecutor.java

    public void queryUserByname() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
Map<String, String> user1 = userMapper1.getUserByname("liSi");
System.out.println("第一次查询:" + user1.toString());
sqlSession1.close(); /*更新操作*/
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper2 userMapper3 = sqlSession3.getMapper(UserMapper2.class);
userMapper3.updateUser("zhangSan", "liSi"); SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
Map<String, String> user2 = userMapper2.getUserByname("liSi");
System.out.println("第二次查询:" + user2.toString());
sqlSession1.close();
}

从上面的打印日志中可以看出,在进行了更新操作之后,第二次的查询仍然读取了缓存中的数据,造成了脏读,所以mybatis的二级缓存不适用于哪些对数据实时性要求比较高的场景中。

认识Mybatis的一二级缓存的更多相关文章

  1. java架构之路-(源码)mybatis的一二级缓存问题

    上次博客我们说了mybatis的基本使用,我们还捎带提到一下Mapper.xml中的select标签的useCache属性,这个就是设置是否存入二级缓存的. 回到我们正题,经常使用mybatis的小伙 ...

  2. MyBatis的一二级缓存

    一级缓存 一级缓存默认是开启的,生命周期和SqlSession相同.一个会话中每次执行一个查询操作时,会先查询二级缓存,如果二级缓存没查到或者二级缓存未开启就会从一级缓存中查询,如果一级缓存也未查到就 ...

  3. 【MyBatis源码解析】MyBatis一二级缓存

    MyBatis缓存 我们知道,频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相 ...

  4. mybatis 源码分析(四)一二级缓存分析

    本篇博客主要讲了 mybatis 一二级缓存的构成,以及一些容易出错地方的示例分析: 一.mybatis 缓存体系 mybatis 的一二级缓存体系大致如下: 首先当一二级缓存同时开启的时候,首先命中 ...

  5. Mybatis学习(6)动态加载、一二级缓存

    一.动态加载: resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能. 需求: 如 ...

  6. Mybatis一二级缓存的理解

        频繁的数据库操作是非常耗费性能的(主要是因为对于DB而言,数据是持久化在磁盘中的,因此查询操作需要通过IO,IO操作速度相比内存操作速度慢了好几个量级),尤其是对于一些相同的查询语句,完全可以 ...

  7. [原创]关于mybatis中一级缓存和二级缓存的简单介绍

    关于mybatis中一级缓存和二级缓存的简单介绍 mybatis的一级缓存: MyBatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候 ...

  8. MyBatis学习--查询缓存

    简介 以前在使用Hibernate的时候知道其有一级缓存和二级缓存,限制ORM框架的发展都是互相吸收其他框架的优点,在Hibernate中也有一级缓存和二级缓存,用于减轻数据压力,提高数据库性能. m ...

  9. Mybatis的二级缓存配置

    一个项目中肯定会存在很多共用的查询数据,对于这一部分的数据,没必要每一个用户访问时都去查询数据库,因此配置二级缓存将是非常必要的.  Mybatis的二级缓存配置相当容易,要开启二级缓存,只需要在你的 ...

随机推荐

  1. 2-18-搭建mysql集群实现高可用

          1 环境清理以及安装 1.1  mysql旧版本清除 准备5台虚拟机,分配如下 mysql管理结点:xuegod1.cn IP:192.168.10.31  (安装server.clien ...

  2. 经典面试编程题--atoi()函数的实现(就是模拟手算,核心代码就一句total = 10 * total + (c - '0'); 但是要注意正负号、溢出等问题)

    一.功能简介 把一个字符串转换成整数 二.linux c库函数实现 /*** *long atol(char *nptr) - Convert string to long * *Purpose: * ...

  3. QML中文件的加载(三种方法)

    在这里小小总结一下QML文件中如何加载QML文件与JavaScript文件. 1.QML文件中加载JavaScript文件 语法: import <ModuleIdentifier> &l ...

  4. SpringBoot简易搭建

    1.建立maven工程 2.打开pom文件, 将以下配置拷贝过去 <parent> <groupId>org.springframework.boot</groupId& ...

  5. iOS-HTTP浅析

    HTTP原理 什么是URL URL中常见的几种协议 什么是HTTP协议 HTTP是做什么的 为什么要使用HTTP协议 HTPP协议的通信过程介绍 HTTP请求 HTTP响应 HTTP请求的选择 两种发 ...

  6. Win10《芒果TV》商店版更新v3.7.0卡牌版:为小冰生日献礼,为秋季创意者更新铺路

    由宇宙跨物种新晋主持微软小冰和绍刚叔联袂主持的中国首档原创顶尖科技秀<我是未来>正在芒果台热播,Win10版<芒果TV>更新v3.7.0卡牌版,为微软小冰9月17日生日献礼. ...

  7. C#串口控制舵机、arduino源码 及C#源码及界面

    原文 C#串口控制舵机.arduino源码 及C#源码及界面 1.舵机原理简介 控制信号由接收机的通道进入信号调制芯片,获得直流偏置电压.它内部有一个基准电路,产生周期为20ms,宽度为1.5ms的基 ...

  8. FastMM、FastCode、FastMove的使用(详细讲解了怎么安装与使用)good

    http://blog.csdn.net/akof1314/article/details/6524767

  9. 流程图浅析MFC架构

    http://blog.csdn.net/qq2399431200/article/details/9035315

  10. 开源玩家福利:十大Linux免费游戏

    假如当你考虑从Windows平台迁移至Linux平台时,“我能在Linux平台上游戏吗?”这类疑问正困扰着你,那么对此这有一个答案就是“快去Linux平台吧!”.感谢开源组织一直以来坚持不懈为Linu ...