前言

什么是mybatis二级缓存?

二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace。

即,在不同的sqlsession中,相同的namespace下,相同的sql语句,并且sql模板中参数也相同的,会命中缓存。

第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。

Mybatis默认没有开启二级缓存,需要在全局配置(mybatis-config.xml)中开启二级缓存。

本文讲述的是使用Redis作为缓存,与springboot、mybatis进行集成的方法。

1、pom依赖

使用springboot redis集成包,方便redis的访问。redis客户端选用Jedis。

另外读写kv缓存会进行序列化,所以引入了一个序列化包。

  1. <dependency>
  2. <groupId>org.springframework.boot</groupId>
  3. <artifactId>spring-boot-starter-redis</artifactId>
  4. </dependency>
  5. <dependency>
  6. <groupId>redis.clients</groupId>
  7. <artifactId>jedis</artifactId>
  8. <version>2.8.0</version>
  9. </dependency>
  10. <dependency>
  11. <groupId>com.alibaba</groupId>
  12. <artifactId>fastjson</artifactId>
  13. <version>1.2.19</version>
  14. </dependency>

依赖搞定之后,下一步先调通Redis客户端。

2、Redis访问使用的Bean

增加Configuration,配置jedisConnectionFactory bean,留待后面使用。

一般来讲,也会生成了redisTemplate bean,但是在接下来的场景没有使用到。

  1. @Configuration
  2. public class RedisConfig {
  3.  
  4. @Value("${spring.redis.host}")
  5. private String host;
  6. // 篇幅受限,省略了
  7.  
  8. @Bean
  9. public JedisPoolConfig getRedisConfig(){
  10. JedisPoolConfig config = new JedisPoolConfig();
  11. config.setMaxIdle(maxIdle);
  12. config.setMaxTotal(maxTotal);
  13. config.setMaxWaitMillis(maxWaitMillis);
  14. config.setMinIdle(minIdle);
  15. return config;
  16. }
  17.  
  18. @Bean(name = "jedisConnectionFactory")
  19. public JedisConnectionFactory getConnectionFactory(){
  20. JedisConnectionFactory factory = new JedisConnectionFactory();
  21. JedisPoolConfig config = getRedisConfig();
  22. factory.setPoolConfig(config);
  23. factory.setHostName(host);
  24. factory.setPort(port);
  25. factory.setDatabase(database);
  26. factory.setPassword(password);
  27. factory.setTimeout(timeout);
  28. return factory;
  29. }
  30.  
  31. @Bean(name = "redisTemplate")
  32. public RedisTemplate<?, ?> getRedisTemplate(){
  33. RedisTemplate<?,?> template = new StringRedisTemplate(getConnectionFactory());
  34. return template;
  35. }
  36. } 

这里使用@Value读入了redis相关配置,有更简单的配置读取方式(@ConfigurationProperties(prefix=...)),可以尝试使用。

Redis相关配置如下

  1. #redis
  2. spring.redis.host=10.93.84.53
  3. spring.redis.port=6379
  4. spring.redis.password=bigdata123
  5. spring.redis.database=15
  6. spring.redis.timeout=0
  7.  
  8. spring.redis.pool.maxTotal=8
  9. spring.redis.pool.maxWaitMillis=1000
  10. spring.redis.pool.maxIdle=8
  11. spring.redis.pool.minIdle=0

Redis客户端的配置含义,这里不再讲解了。pool相关的一般都和性能有关,需要根据并发量权衡句柄、内存等资源进行设置。

Redis客户端设置好了,我们开始配置Redis作为Mybatis的缓存。

3、Mybatis Cache

这一步是最为关键的一步。实现方式是实现Mybatis的一个接口org.apache.ibatis.cache.Cache。

这个接口设计了写缓存,读缓存,销毁缓存的方式,和访问控制读写锁。

我们实现实现Cache接口的类是MybatisRedisCache。

MybatisRedisCache.java

  1. public class MybatisRedisCache implements Cache {
  2.  
  3. private static JedisConnectionFactory jedisConnectionFactory;
  4.  
  5. private final String id;
  6.  
  7. private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  8.  
  9. public MybatisRedisCache(final String id) {
  10. if (id == null) {
  11. throw new IllegalArgumentException("Cache instances require an ID");
  12. }
  13. this.id = id;
  14. }
  15.  
  16. @Override
  17. public void clear() {
  18. RedisConnection connection = null;
  19. try {
  20. connection = jedisConnectionFactory.getConnection();
  21. connection.flushDb();
  22. connection.flushAll();
  23. } catch (JedisConnectionException e) {
  24. e.printStackTrace();
  25. } finally {
  26. if (connection != null) {
  27. connection.close();
  28. }
  29. }
  30. }
  31.  
  32. @Override
  33. public String getId() {
  34. return this.id;
  35. }
  36.  
  37. @Override
  38. public Object getObject(Object key) {
  39. Object result = null;
  40. RedisConnection connection = null;
  41. try {
  42. connection = jedisConnectionFactory.getConnection();
  43. RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
  44. result = serializer.deserialize(connection.get(serializer.serialize(key)));
  45. } catch (JedisConnectionException e) {
  46. e.printStackTrace();
  47. } finally {
  48. if (connection != null) {
  49. connection.close();
  50. }
  51. }
  52. return result;
  53. }
  54.  
  55. @Override
  56. public ReadWriteLock getReadWriteLock() {
  57. return this.readWriteLock;
  58. }
  59.  
  60. @Override
  61. public int getSize() {
  62. int result = 0;
  63. RedisConnection connection = null;
  64. try {
  65. connection = jedisConnectionFactory.getConnection();
  66. result = Integer.valueOf(connection.dbSize().toString());
  67. } catch (JedisConnectionException e) {
  68. e.printStackTrace();
  69. } finally {
  70. if (connection != null) {
  71. connection.close();
  72. }
  73. }
  74. return result;
  75. }
  76.  
  77. @Override
  78. public void putObject(Object key, Object value) {
  79. RedisConnection connection = null;
  80. try {
  81. connection = jedisConnectionFactory.getConnection();
  82. RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
  83. connection.set(serializer.serialize(key), serializer.serialize(value));
  84. } catch (JedisConnectionException e) {
  85. e.printStackTrace();
  86. } finally {
  87. if (connection != null) {
  88. connection.close();
  89. }
  90. }
  91. }
  92.  
  93. @Override
  94. public Object removeObject(Object key) {
  95. RedisConnection connection = null;
  96. Object result = null;
  97. try {
  98. connection = jedisConnectionFactory.getConnection();
  99. RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
  100. result = connection.expire(serializer.serialize(key), 0);
  101. } catch (JedisConnectionException e) {
  102. e.printStackTrace();
  103. } finally {
  104. if (connection != null) {
  105. connection.close();
  106. }
  107. }
  108. return result;
  109. }
  110.  
  111. public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
  112. MybatisRedisCache.jedisConnectionFactory = jedisConnectionFactory;
  113. }
  114.  
  115. }

注意:

可以看到,这个类并不是由Spring虚拟机管理的类,但是,其中有一个静态属性jedisConnectionFactory需要注入一个Spring bean,也就是在RedisConfig中生成的bean。

在一个普通类中使用Spring虚拟机管理的Bean,一般使用Springboot自省的SpringContextAware。

这里使用了另一种方式,静态注入的方式。这个方式是通过RedisCacheTransfer来实现的。

4、静态注入

RedisCacheTransfer.java

  1. @Component
  2. public class RedisCacheTransfer {
  3.  
  4. @Autowired
  5. public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
  6. MybatisRedisCache.setJedisConnectionFactory(jedisConnectionFactory);
  7. }
  8.  
  9. }

可以看到RedisCacheTransfer是一个springboot bean,在容器创建之初进行初始化的时候,会注入jedisConnectionFactory bean给setJedisConnectionFactory方法的传参。

而setJedisConnectionFactory通过调用静态方法设置了类MybatisRedisCache的静态属性jedisConnectionFactory。

这样就把spring容器管理的jedisConnectionFactory注入到了静态域。

到这里,代码基本已经搞定,下面是一些配置。主要有(1)全局开关;(2)namespace作用域开关;(3)Model实例序列化。

5、Mybatis二级缓存的全局开关

前面提到过,默认二级缓存没有打开,需要设置为true。这是全局二级缓存的开关。

Mybatis的全局配置。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2.  
  3. <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
  4.  
  5. <configuration>
  6. <!-- 全局参数 -->
  7. <settings>
  8. <!-- 使全局的映射器启用或禁用缓存。 -->
  9. <setting name="cacheEnabled" value="true"/>
  10. </settings>
  11.  
  12. </configuration>

全局配置的加载在dataSource中可以是这样的。

bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis-mapper/*.xml"));

指定了mapper.xml的存放路径,在mybatis-mapper路径下,所有后缀是.xml的都会读入。

bean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));

指定了mybatis-config.xml的存放路径,直接放在Resource目录下即可。

  1. @Bean(name = "moonlightSqlSessionFactory")
  2. @Primary
  3. public SqlSessionFactory moonlightSqlSessionFactory(@Qualifier("moonlightData") DataSource dataSource) throws Exception {
  4. SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
  5. bean.setDataSource(dataSource);
  6. bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis-mapper/*.xml"));
  7. bean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
  8. return bean.getObject();
  9. }

6、配置mapper作用域namespace

前面提到过,二级缓存的作用域是mapper的namespace,所以这个配置需要到mapper中去写。

  1. <mapper namespace="com.kangaroo.studio.moonlight.dao.mapper.MoonlightMapper">
  2. <cache type="com.kangaroo.studio.moonlight.dao.cache.MybatisRedisCache"/>
  3. <resultMap id="geoFenceList" type="com.kangaroo.studio.moonlight.dao.model.GeoFence">
  4. <constructor>
  5. <idArg column="id" javaType="java.lang.Integer" jdbcType="INTEGER" />
  6. <arg column="name" javaType="java.lang.String" jdbcType="VARCHAR" />
  7. <arg column="type" javaType="java.lang.Integer" jdbcType="INTEGER" />
  8. <arg column="group" javaType="java.lang.String" jdbcType="VARCHAR" />
  9. <arg column="geo" javaType="java.lang.String" jdbcType="VARCHAR" />
  10. <arg column="createTime" javaType="java.lang.String" jdbcType="VARCHAR" />
  11. <arg column="updateTime" javaType="java.lang.String" jdbcType="VARCHAR" />
  12. </constructor>
  13. </resultMap>
  14.  
  15. <select id="queryGeoFence" parameterType="com.kangaroo.studio.moonlight.dao.model.GeoFenceQueryParam" resultMap="geoFenceList">
  16. select <include refid="base_column"/> from geoFence where 1=1
  17. <if test="type != null">
  18. and type = #{type}
  19. </if>
  20. <if test="name != null">
  21. and name like concat('%', #{name},'%')
  22. </if>
  23. <if test="group != null">
  24. and `group` like concat('%', #{group},'%')
  25. </if>
  26. <if test="startTime != null">
  27. and createTime &gt;= #{startTime}
  28. </if>
  29. <if test="endTime != null">
  30. and createTime &lt;= #{endTime}
  31. </if>
  32. </select>
  33. </mapper>

注意:

namespace下的cache标签就是加载缓存的配置,缓存使用的正式我们刚才实现的MybatisRedisCache。

  1. <cache type="com.kangaroo.studio.moonlight.dao.cache.MybatisRedisCache"/>

这里只实现了一个查询queryGeoFence,你可以在select标签中,开启或者关闭这个sql的缓存。使用属性值useCache=true/false。

7、Mapper和Model

读写缓存Model需要序列化:只需要类声明的时候实现Seriaziable接口就好了。

  1. public class GeoFence implements Serializable {
  2. // setter和getter省略
  3. }
  4. public class GeoFenceParam implements Serializable {
  5. // setter和getter省略
  6. }

mapper就还是以前的写法,使用mapper.xml的方式这里只需要定义出抽象函数即可。

  1. @Mapper
  2. public interface MoonlightMapper {
  3. List<GeoFence> queryGeoFence(GeoFenceQueryParam geoFenceQueryParam);
  4. }

到这里,所有的代码和配置都完成了,下面测试一下。

8、测试一下

Controller中实现一个这样的接口POST。

  1. @RequestMapping(value = "/fence/query", method = RequestMethod.POST)
  2. @ResponseBody
  3. public ResponseEntity<Response> queryFence(@RequestBody GeoFenceQueryParam geoFenceQueryParam) {
  4. try {
  5. Integer pageNum = geoFenceQueryParam.getPageNum()!=null?geoFenceQueryParam.getPageNum():1;
  6. Integer pageSize = geoFenceQueryParam.getPageSize()!=null?geoFenceQueryParam.getPageSize():10;
  7. PageHelper.startPage(pageNum, pageSize);
  8. List<GeoFence> list = moonlightMapper.queryGeoFence(geoFenceQueryParam);
  9. return new ResponseEntity<>(
  10. new Response(ResultCode.SUCCESS, "查询geoFence成功", list),
  11. HttpStatus.OK);
  12. } catch (Exception e) {
  13. logger.error("查询geoFence失败", e);
  14. return new ResponseEntity<>(
  15. new Response(ResultCode.EXCEPTION, "查询geoFence失败", null),
  16. HttpStatus.INTERNAL_SERVER_ERROR);
  17. }

使用curl发送请求,注意

1)-H - Content-type:application/json方式

2)-d - 后面是json格式的参数包体

  1. curl -H "Content-Type:application/json" -XPOST http://。。。/moonlight/fence/query -d '{
  2. "name" : "test",
  3. "group": "test",
  4. "type": 1,
  5. "startTime":"2017-12-06 00:00:00",
  6. "endTime":"2017-12-06 16:00:00",
  7. "pageNum": 1,
  8. "pageSize": 8
  9. }'

请求了三次,日志打印如下,

可以看到,只有第一次执行了sql模板查询,后面都是命中了缓存。

在我们的测试环境中由于数据量比较小,缓存对查询速度的优化并不明显。这里就不过多说明了。

最后上一篇打脸文。给你参考http://blog.csdn.net/isea533/article/details/44566257

完毕。

springboot mybatis redis 二级缓存的更多相关文章

  1. Spring Boot + Mybatis + Redis二级缓存开发指南

    Spring Boot + Mybatis + Redis二级缓存开发指南 背景 Spring-Boot因其提供了各种开箱即用的插件,使得它成为了当今最为主流的Java Web开发框架之一.Mybat ...

  2. 使用Redis做MyBatis的二级缓存

    使用Redis做MyBatis的二级缓存 通常为了减轻数据库的压力,我们会引入缓存.在Dao查询数据库之前,先去缓存中找是否有要找的数据,如果有则用缓存中的数据即可,就不用查询数据库了. 如果没有才去 ...

  3. springboot+mybatis+redis实现分布式缓存

    大家都知道springboot项目都是微服务部署,A服务和B服务分开部署,那么它们如何更新或者获取共有模块的缓存数据,或者给A服务做分布式集群负载,如何确保A服务的所有集群都能同步公共模块的缓存数据, ...

  4. Mybatis使用Redis二级缓存

    在Mybatis中允许开发者自定义自己的缓存,本文将使用Redis作为Mybatis的二级缓存.在Mybatis中定义二级缓存,需要如下配置: 1. MyBatis支持二级缓存的总开关:全局配置变量参 ...

  5. Redis实现Mybatis的二级缓存

    一.Mybatis的缓存 通大多数ORM层框架一样,Mybatis自然也提供了对一级缓存和二级缓存的支持.一下是一级缓存和二级缓存的作用于和定义. 1.一级缓存是SqlSession级别的缓存.在操作 ...

  6. mybatis整合redis二级缓存

    mybatis默认开启了二级缓存功能,在mybatis主配置文件中,将cacheEnabled设置成false,则会关闭二级缓存功能 <settings> <!--二级缓存默认开启, ...

  7. Mybatis的二级缓存、使用Redis做二级缓存

    目录 什么是二级缓存? 1. 开启二级缓存 如何使用二级缓存: userCache和flushCache 2. 使用Redis实现二级缓存 如何使用 3. Redis二级缓存源码分析 什么是二级缓存? ...

  8. MyBatis:二级缓存原理分析

    MyBatis从入门到放弃七:二级缓存原理分析 前言 说起mybatis的一级缓存和二级缓存我特意问了几个身边的朋友他们平时会不会用,结果没有一个人平时业务场景中用. 好吧,那我暂且用来学习源码吧.一 ...

  9. Mybatis的二级缓存配置

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

随机推荐

  1. MySQL常用配置参数

    基本配置: datadir:指定mysql的数据目录位置,用于存放mysql数据库文件.日志文件等. 配置示例:datadir=D:/wamp/mysqldata/Data default-chara ...

  2. 温故而知新 Volley源码解读与思考

    相比新的网络请求框架Volley真的很落后,一无是处吗,要知道Volley是由google官方推出的,虽然推出的时间很久了,但是其中依然有值得学习的地方.  从命名我们就能看出一些端倪,volley中 ...

  3. JNI 对象处理 (转)

    JNI 的基本问题就是解决 Java 和 C++ 代码互相调用的通信问题,在 C++ 代码编写过程中最大的问题莫过于适应其中的代码编写规则,C++调用或是返回的内容必须遵守 JVM 和 C++ 代码的 ...

  4. MySQL数据库储存bit类型的值报错

    当我们储存bit类型的值时,不能直接写入数字 上图中的画圈部分就是bit类型,若是直接填入"1"或"0"等等就会报错,如下: 这时候,我们要看bit(M)的M值 ...

  5. VS2008 C++ 利用WinHttp API获取任意Http网址的源码

    最近一直在看有关Http的知识,对其基本的理论知识已经有所掌握,想通过一个C++具体的例子进行实际操作..于是上网查找了很多资料,发现在Windows系统上,可以通过WinHttp API接口开啊Ht ...

  6. Hello Kiki(中国剩余定理——不互质的情况)

    Hello Kiki Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Su ...

  7. Linux学习(十七)压缩与打包

    一.关于打包和压缩 打包和压缩的最大意义在于减少文件传输中需要的流量.打包的方式大概有tar命令,zip命令.压缩的方式有gzip,bzip2,xz.tar命令可以通过参数将压缩和打包在一起执行. 二 ...

  8. Elasticsearch集群调优

    系统调优 禁用swap 使用swapoff命令可以暂时关闭swap.永久关闭需要编辑/etc/fstab,注释掉swap设备的挂载项. swapoff -a 如果完全关闭swap不可行,可以试着降低s ...

  9. 获取标签的src属性兼容性

    获取节点如script标签的src属性时,针对非IE6,IE7可以直接使用src属性,但在IE6-7中存在问题,可以借助getAttribute方法 getAttribute(attr,iflag) ...

  10. ⑤bootstrap表格使用基础案例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...