认识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. mysql 优化 读写分离 主从复制

    1:mysql所在服务器内核 优化  ---------此优化可由系统运维人员完成 2:mysql配置参数优化(my.cnf) -------- 此优化需进行压力测试来进行参数调整 3:sql语句及表 ...

  2. DOM解析xml实现读、写、增、删、改

    qt提供了三种方式解析xml,不过如果想实现对xml文件进行增.删.改等操作,还是DOM方式最方便. 项目配置 pro文件里面添加QT+=xml include <QtXml>,也可以in ...

  3. oracle 12c连接pdb

    12c中,如何连接pluggable database: 使用默认的service连接pdb,创建pdb之后,在监听中自动添加以pdb为名的service: 用户在cluster中创建service, ...

  4. Delphi在系统菜单中添加菜单项

    unit dy219; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms ...

  5. Android Java调用Qt写的so库

    有时候,我们反编译apk得到一个so库,如果直接使用这个so库的话,必须使用原来so库同样的package名字,才能用.这样人家反编译你的apk,就知道你侵犯了人家的版权.为了达到混淆的目的,我们可以 ...

  6. CPU多核控速

    初学者很多对自己开发的软件使用硬件资源的时候并不注意,造成写出的东西不是很满意. 一般有两种情况: 1.写的都是同步单线程任务,不管你电脑有多少个核都不关我事 我就用你1个核所以不管怎么样都不会把CP ...

  7. Delphi获得一个进程的主窗体(GetWindow(AHandle, GW_OWNER)等于0的窗体才是主窗体,并且要IsWindowVisible排除Application窗口)

    type  TMainWindow = packed record    ProcessID: THandle;    MainWindow: THandle;  end;  PMainWindow  ...

  8. 发布Qt Quick桌面应用程序的方法(使得planets在XP上运行)

    发布Qt Quick桌面应用程序的方法 Qt是一款优秀的跨平台开发框架,它可以在桌面.移动平台以及嵌入式平台上运行.目前Qt 5介绍程序发布的文章帖子比较少.大家又非常想要知道如何发布Qt应用程序,于 ...

  9. Codlility---MinPerimeterRectangle

    Task description An integer N is given, representing the area of some rectangle. The area of a recta ...

  10. MongoDB自学日记3——架构及HA

    在对mongoDB的操作有了一定基础后,终于可以扯扯HA和架构这两个高大上的概念了.在这之前当然还得弄清楚mongoDB的Key feature:Sharding. 1. Sharding Shard ...