1、二级缓存

二级缓存的原理和一级缓存原理一样,第一次查询,会将数据放入缓存中,然后第二次查询则会直接去缓存中取。但是一级缓存是基于sqlSession的,而二级缓存是基于mapper文件的namespace的,也就是说多个sqlSession可以共享一个mapper中的二级缓存区域,并且如果两个mapper的namespace 相同,即使是两个mapper,那么这两个mapper中执行sql查询到的数据也将存在相同的二级缓存区域中。

如何使用二级缓存

1.1、开启二级缓存

和一级缓存默认开启不一样,二级缓存需要我们手动开启首先在全局配置文件sqlMapConfig.xml文件中加入如下代码

<!--开启二级缓存-->

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

其次在UserMapper.xml文件中开启缓存(如果我们是基于注解形式进行查询,可以在mapper查询接口上添加@CacheNamespace注解开启二级缓存)

方式一:在xml文件中配置

 <!--开启二级缓存-->
<cache></cache>

方式二:在mapper接口上添加上

@CacheNamespace

后面可以带参数指定加载自定义的缓存实现类

@CacheNamespace(implementation = RedisCache.class)//开启二级缓存

我们可以看到mapper.xml文件中就这么一个空标签,其实这里可以配置,PerpetualCache这个类是mybatis默认实现缓存功能的类。我们不写type就使用mybatis默认的缓存,也可以去实现Cache接口来自定义缓存。

public class PerpetualCache implements Cache {private final String id;private MapcObject, Object> cache = new HashMapC);public PerpetualCache(St ring id) { this.id = id;
}

我们可以看到二级缓存底层还是HashMap结构

public class User implements Serializable(//⽤户IDprivate int id;//⽤户姓名private String username;//⽤户性别private String sex;
}

注意:

开启了二级缓存后,还需要将要缓存的pojo实现Serializable接口,为了将缓存数据取出执行反序列化操作,因为二级缓存数据存储介质多种多样,不一定只存在内存中,有可能存在硬盘中,如果我们要再取这个缓存的话,就需要反序列化了。所以mybatis中的pojo都去实现Serializable接口。

1.2、测试

1.2.1、测试⼆级缓存和sqlSession无关

@Test
public void testTwoCache(){//根据 sqlSessionFactory 产⽣ sessionSqlSession sqlSession1 = sessionFactory.openSession();SqlSession sqlSession2 = sessionFactory.openSession();UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class );UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class );//第⼀次查询,发出sql语句,并将查询的结果放⼊缓存中User u1 = userMapper1.selectUserByUserId(1);System.out.println(u1);sqlSession1.close(); //第⼀次查询完后关闭 sqlSession ,sqlsession关闭后就会清除一级缓存//第⼆次查询,即使sqlSession1已经关闭了,这次查询依然不发出sql语句User u2 = userMapper2.selectUserByUserId(1);System.out.println(u2);sqlSession2.close();
}

可以看出上面两个不同的sqlSession,第一个关闭了,第二次查询依然不发出sql查询语句
1.2.2、测试执行commit()操作,二级缓存数据清空

@Test
public void testTwoCache(){//根据 sqlSessionFactory 产⽣ sessionSqlSession sqlSession1 = sessionFactory.openSession();SqlSession sqlSession2 = sessionFactory.openSession();SqlSession sqlSession3 = sessionFactory.openSession();String statement = "com.lagou.pojo.UserMapper.selectUserByUserld" ;UserMapper userMapper1 = sqlSession1.getMapper(UserMapper. class );UserMapper userMapper2 = sqlSession2.getMapper(UserMapper. class );UserMapper userMapper3 = sqlSession2.getMapper(UserMapper. class );//第⼀次查询,发出sql语句,并将查询的结果放⼊缓存中User u1 = userMapperl.selectUserByUserId( 1 );System.out.println(u1);sqlSessionl .close(); //第⼀次查询完后关闭sqlSession//执⾏更新操作,commit()u1.setUsername( "aaa" );userMapper3.updateUserByUserId(u1);sqlSession3.commit();//第⼆次查询,由于上次更新操作,缓存数据已经清空(防⽌数据脏读),这⾥必须再次发出sql语User u2 = userMapper2.selectUserByUserId( 1 );System.out.println(u2);sqlSession2.close();
}

查看控制台情况:

1.3、useCache和flushCache

mybatis中还可以配置userCacheflushCache等配置项,userCache是用来设置是否禁用二级缓 存的,在statement中设置useCache=false可以禁⽤当前select语句的二级缓存,即每次查询都会发出sql去查询,默认情况是true,即该sql使用二级缓存,还可以使用注解方式禁用@Options(useCache=false),flushCache的注解配置方法同上 。

方法一:

<select id="selectUserByUserId" useCache="false"
        resultType="com.lagou.pojo.User" parameterType="int">
                select * from user where id=#{id}
</select>

方法二:

@Options(useCache = false)
@Select({"select * from user where id = #{id}"})
public User findUserById(Integer id);

  • 这种情况是针对每次查询都需要最新的数据sql,要设置成useCache=false,禁用二级缓存,直接从数 据库中获取。
  • 在mapper的同一个namespace中,如果有其它insert、update, delete操作数据后需要刷新缓 存,如果不执行刷新缓存会出现脏读。
  • 设置statement配置中的flushCache="true”属性,默认情况下为true,即刷新缓存,如果改成false则 不会刷新。使用缓存时如果⼿动修改数据库表中的查询数据会出现脏读。

方法一:

<select id="selectUserByUserId" flushCache="true" useCache="false"
        resultType="com.lagou.pojo.User" parameterType="int">
                select * from user where id=#{id}
</select>

方法二:

@Options({useCache = false, flushCache="true"})
@Select({"select * from user where id = #{id}"})
public User findUserById(Integer id);

一般下执行完commit操作都需要刷新缓存,flushCache=true表示刷新缓存,这样可以避免数据库脏读。所以我们不用设置,默认即可

2、二级缓存整合redis

上面我们介绍了 mybatis自带的二级缓存,但是这个缓存是单服务器工作,无法实现分布式缓存。 那么什么是分布式缓存呢?假设现在有两个服务器1和2,用户访问的时候访问了 1服务器,查询后的缓存就会放在1服务器上,假设现在有个用户访问的是2服务器,那么他在2服务器上就无法获取刚刚那个缓存,如下图所示:

为了解决这个问题,就得找一个分布式的缓存,专门用来存储缓存数据的,这样不同的服务器要缓存数据都往它那里存,取缓存数据也从它那里取,如下图所示:

如上图所示,在几个不同的服务器之间,我们使用第三方缓存框架,将缓存都放在这个第三方框架中,然后无论有多少台服务器,我们都能从缓存中获取数据。

这里我们介绍mybatis与redis的整合。
        刚刚提到过,mybatis提供了一个eache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。mybatis本身默认实现了一个,但是这个缓存的实现无法实现分布式缓存,所以我们要自己来实现。redis分布式缓存就可以,mybatis提供了一个针对cache接口的redis实现类,该类存在mybatis-redis包中。

实现:

2.1、pom文件

<dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-redis</artifactId>
        <version>1.0.0-beta2</version>
</dependency>

2.2、配置文件

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.lagou.mapper.IUserMapper"><cache type="org.mybatis.caches.redis.RedisCache" /> 二级缓存类地址<select id="findAll" resultType="com.lagou.pojo.User" useCache="true">select * from user
</select> 

2.3、redis.properties (名字必须叫这个,不然无法获取到里面的数据)

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

2.4、测试

@Test
public void SecondLevelCache(){SqlSession sqlSession1 = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();SqlSession sqlSession3 = sqlSessionFactory.openSession();IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);lUserMapper mapper2 = sqlSession2.getMapper(lUserMapper.class);lUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);User user1 = mapper1.findUserById(1);sqlSession1.close(); //清空⼀级缓存User user = new User();user.setId(1);user.setUsername("lisi");mapper3.updateUser(user);sqlSession3.commit();User user2 = mapper2.findUserById(1);System.out.println(user1==user2);
}

2.5、源码分析

RedisCache和大家普遍实现Mybatis的缓存方案大同小异,无非是实现Cache接口,并使用jedis操作缓存;不过该项目在设计细节上有一些区别;

public final class RedisCache implements Cache {
public RedisCache(final String id) {if (id == null) {throw new IllegalArgumentException("Cache instances require anID");}this.id = id;RedisConfig redisConfig =RedisConfigurationBuilder.getInstance().parseConfiguration();pool = new JedisPool(redisConfig, redisConfig.getHost(),redisConfig.getPort(),redisConfig.getConnectionTimeout(),redisConfig.getSoTimeout(), redisConfig.getPassword(),redisConfig.getDatabase(), redisConfig.getClientName());
}

RedisCache在mybatis启动的时候,由MyBatis的CacheBuilder创建,创建的方式很简单,就是调用RedisCache的带有String参数的构造方法,即RedisCache(String id);而在RedisCache的构造方法中,调用了 RedisConfigu rationBuilder 来创建 RedisConfig 对象,并使用 RedisConfig 来创建JedisPool。

RedisConfig类继承了 JedisPoolConfig,并提供了 host,port等属性的包装,简单看⼀下RedisConfig的属性:

public class RedisConfig extends JedisPoolConfig {
private String host = Protocol.DEFAULT_HOST;
private int port = Protocol.DEFAULT_PORT;
private int connectionTimeout = Protocol.DEFAULT_TIMEOUT;
private int soTimeout = Protocol.DEFAULT_TIMEOUT;
private String password;
private int database = Protocol.DEFAULT_DATABASE;
private String clientName;

RedisConfig对象是由RedisConfigurationBuilder创建的,简单看下这个类的主要方法:

public RedisConfig parseConfiguration(ClassLoader classLoader) {Properties config = new Properties();InputStream input = classLoader.getResourceAsStream(redisPropertiesFilename);if (input != null) {try {config.load(input);} catch (IOException e) {throw new RuntimeException("An error occurred while reading classpath property '"+ redisPropertiesFilename+ "', see nested exceptions", e);} finally {try {input.close();} catch (IOException e) {// close quietly}}}RedisConfig jedisConfig = new RedisConfig();setConfigProperties(config, jedisConfig);return jedisConfig;
}

核心的方法就是parseConfiguration方法,该方法从classpath中读取一个redis.properties⽂件:

host=localhost
port=6379
connectionTimeout=5000
soTimeout=5000
password= database=0 clientName=

并将该配置文件中的内容设置到RedisConfig对象中,并返回;接下来,就是RedisCache使用RedisConfig类创建完成edisPool;在RedisCache中实现了一个简单的模板方法,用来操作Redis:

private Object execute(RedisCallback callback) {Jedis jedis = pool.getResource();try {return callback.doWithRedis(jedis);} finally {jedis.close();}
}

模板接口为RedisCallback,这个接口中就只需要实现了一个doWithRedis方法而已:

public interface RedisCallback {Object doWithRedis(Jedis jedis);
}

接下来看看Cache中最重要的两个方法:putObject和getObject,通过这两个方法来查看mybatis-redis储存数据的格式:

 @Overridepublic void putObject(final Object key, final Object value) {execute(new RedisCallback() {@Overridepublic Object doWithRedis(Jedis jedis) {jedis.hset(id.toString().getBytes(), key.toString().getBytes(),SerializeUtil.serialize(value));return null;}});}@Overridepublic Object getObject(final Object key) {return execute(new RedisCallback() {@Overridepublic Object doWithRedis(Jedis jedis) {return SerializeUtil.unserialize(jedis.hget(id.toString().getBytes(),key.toString().getBytes()));}});}

可以很清楚的看到,mybatis-redis在存储数据的时候,是使用的hash结构,把cache的id作为这个hash的key (cache的id在mybatis中就是mapper的namespace);这个mapper中的查询缓存数据作为 hash的field,需要缓存的内容直接使用SerializeUtil存储,SerializeUtil和其他的序列化类差不多,负责对象的序列化和反序列化;

Mybatis 之 二级缓存相关推荐

  1. Mybatis集成二级缓存与同时使用缓存与事务存在的坑

    今天在看分布式事务的时候,突然收到app不能签到的消息,赶紧解决. 具体解决方法: 1.把执行错误的处理方法提取出来,作为测试方法 2.这个方法里面有两个插入语句,一条查询语句,一个更新语句,涉及到三 ...

  2. mybatis的二级缓存

    二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的sql语句,多个SqlSession可以共用二级缓存, 二级缓存是跨SqlSession的. 二级缓存结构图 ...

  3. Java Web现代化开发:Spring Boot + Mybatis + Redis二级缓存

    背景 Spring-Boot因其提供了各种开箱即用的插件,使得它成为了当今最为主流的Java Web开发框架之一.Mybatis是一个十分轻量好用的ORM框架.Redis是当今十分主流的分布式key- ...

  4. 【MyBatis笔记12】MyBatis中二级缓存相关配置内容

    这篇文章,主要介绍MyBatis中二级缓存相关配置信息. 目录 一.MyBatis二级缓存 1.1.cache标签相关属性 (1)eviction属性 (2)size属性 (3)flushIntern ...

  5. Springboot 集成 mybatis 开启二级缓存(redis)

    首先来了解下mybatis 缓存,mybatis缓存分为一级缓存和二级缓存.一级缓存是默认开启的,无需其他配置操作,二级缓存则需要手动设置开启. 一级缓存原理: Mybatis的一级缓存是指同一个Sq ...

  6. Mybatis实现二级缓存

    目录 一.Mybatis实现Ehcache作为二级缓存 1.导入相关依赖 2 .修改日志配置,因为ehcache使用了Slf4j作为日志输出 3. 在Resource中添加一个ehcache.xml的 ...

  7. mysql二级缓存redis_SpringBoot+Mybatis+redis(二级缓存)搭建

    刚刚开始接触Spring Boot,因为极简单的配置开发,搭建一个通用的Spring Boot+Mybaitis+redis的开发框架. 一.用maven构建项目,pom.xml文件如下: org.s ...

  8. Mybatis之二级缓存简析

    2019独角兽企业重金招聘Python工程师标准>>> 注:Mybatis的版本是3.5.0. 上一篇分析了一级缓存,这篇来分析二级缓存. 以下的内容,跳过了很多细节,可以去看这篇博 ...

  9. mybatis开启二级缓存和懒加载,类型别名,类都简称

    SqlMapConfig.xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE conf ...

最新文章

  1. python自动办公 pdf_Python办公自动化|批量合并PDF,拿来就用
  2. 继国务院后,上海也发布人工智能发展意见,2020年产业规模超千亿
  3. propertychange 属性说明
  4. LeetCode 21 合并两个有序链表
  5. [BTS06]BizTalk2006 SDK阅读笔记(一) 角色
  6. Linux 程序后台运行与删除
  7. Linux中文件复制、删除、移动、压缩、解压命令
  8. Asset mangagement Resources(完善中)
  9. 计算机高速接口与嵌入式设计与实现,基于嵌入式双结构通信系统高速接口的设计与实现...
  10. torch安装及CUDA和torch版本的对应关系
  11. 安装虚拟机(二)配置静态ip
  12. QT 简单应用之播放WAV声音
  13. Bcm96xx 系列芯片 SDK介绍(二)
  14. 浅谈企业信息化规划建设
  15. 电信移动信号测试软件,移动、联通、电信(信号强度大比拼)
  16. IE浏览器设置默认显示版本
  17. Springboot_vue摄影作品图片展示交流平台
  18. LaTeX 使用心得:节标题(section)中使用数学符号
  19. 在CMD中输入adb命令,提示“‘adb‘ 不是内部或外部命令,也不是可运行程序或批处理文件”的解决方法
  20. Factor_mimicking_portfolio(模仿因子的投资组合):EAP.fama_macbeth.Factor_mimicking_portfolio

热门文章

  1. Python面试知识点小结
  2. sqlserver 人名_SQLserver运维必备:T-SQL语句练习
  3. Remote Procedure Calls
  4. ROS学习笔记3_发布者Publisher
  5. 微盟股价快速飞升的背后:WOS系统将驱动长效增长
  6. GPS卫星位置的计算
  7. 微信小程序项目实例——摇色子
  8. xshell-突出显示集设置
  9. Ubuntu20.04桌面系统快速上手教程
  10. ISO 8601 标准时间格式