Springboot Mybatis Redis 实现二级缓存
前言
什么是mybatis二级缓存?
二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace。
即,在不同的sqlsession中,相同的namespace下,相同的sql语句,并且sql模板中参数也相同的,会命中缓存。
第一次执行完毕会将数据库中查询的数据写到缓存,第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
Mybatis默认没有开启二级缓存,需要在全局配置(mybatis-config.xml)中开启二级缓存。
本文讲述的是使用Redis作为缓存,与springboot、mybatis进行集成的方法。
1、pom依赖
使用springboot redis集成包,方便redis的访问。redis客户端选用Jedis。
另外读写kv缓存会进行序列化,所以引入了一个序列化包。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.0</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.19</version> </dependency>
依赖搞定之后,下一步先调通Redis客户端。
2、Redis访问使用的Bean
增加Configuration,配置jedisConnectionFactory bean,留待后面使用。
一般来讲,也会生成了redisTemplate bean,但是在接下来的场景没有使用到。
@Configuration public class RedisConfig { @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; @Value("${spring.redis.database}") private int database; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.timeout}") private int timeout; @Value("${spring.redis.pool.maxTotal}") private int maxTotal; @Value("${spring.redis.pool.maxWaitMillis}") private int maxWaitMillis; @Value("${spring.redis.pool.maxIdle}") private int maxIdle; @Value("${spring.redis.pool.minIdle}") private int minIdle; @Bean public JedisPoolConfig getRedisConfig(){ JedisPoolConfig config = new JedisPoolConfig(); config.setMaxIdle(maxIdle); config.setMaxTotal(maxTotal); config.setMaxWaitMillis(maxWaitMillis); config.setMinIdle(minIdle); return config; } @Bean(name = "jedisConnectionFactory") public JedisConnectionFactory getConnectionFactory(){ JedisConnectionFactory factory = new JedisConnectionFactory(); JedisPoolConfig config = getRedisConfig(); factory.setPoolConfig(config); factory.setHostName(host); factory.setPort(port); factory.setDatabase(database); factory.setPassword(password); factory.setTimeout(timeout); return factory; } @Bean(name = "redisTemplate") public RedisTemplate<?, ?> getRedisTemplate(){ RedisTemplate<?,?> template = new StringRedisTemplate(getConnectionFactory()); return template; } }
这里使用@Value读入了redis相关配置,有更简单的配置读取方式(@ConfigurationProperties(prefix=...)),可以尝试使用。
Redis相关配置如下
#redis spring.redis.host=10.93.84.53 spring.redis.port=6379 spring.redis.password=bigdata123 spring.redis.database=15 spring.redis.timeout=0 spring.redis.pool.maxTotal=8 spring.redis.pool.maxWaitMillis=1000 spring.redis.pool.maxIdle=8 spring.redis.pool.minIdle=0
Redis客户端的配置含义,这里不再讲解了。pool相关的一般都和性能有关,需要根据并发量权衡句柄、内存等资源进行设置。
Redis客户端设置好了,我们开始配置Redis作为Mybatis的缓存。
3、Mybatis Cache
这一步是最为关键的一步。实现方式是实现Mybatis的一个接口org.apache.ibatis.cache.Cache。
Cache.java
package org.apache.ibatis.cache; import java.util.concurrent.locks.ReadWriteLock; public interface Cache { String getId(); void putObject(Object var1, Object var2); Object getObject(Object var1); Object removeObject(Object var1); void clear(); int getSize(); ReadWriteLock getReadWriteLock(); }
可以看到,这个接口设计了写缓存,读缓存,销毁缓存的方式,和访问控制读写锁。
我们实现实现Cache接口的类是MybatisRedisCache。
MybatisRedisCache.java
public class MybatisRedisCache implements Cache { private static JedisConnectionFactory jedisConnectionFactory; private final String id; private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public MybatisRedisCache(final String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } this.id = id; } @Override public void clear() { RedisConnection connection = null; try { connection = jedisConnectionFactory.getConnection(); connection.flushDb(); connection.flushAll(); } catch (JedisConnectionException e) { e.printStackTrace(); } finally { if (connection != null) { connection.close(); } } } @Override public String getId() { return this.id; } @Override public Object getObject(Object key) { Object result = null; RedisConnection connection = null; try { connection = jedisConnectionFactory.getConnection(); RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer(); result = serializer.deserialize(connection.get(serializer.serialize(key))); } catch (JedisConnectionException e) { e.printStackTrace(); } finally { if (connection != null) { connection.close(); } } return result; } @Override public ReadWriteLock getReadWriteLock() { return this.readWriteLock; } @Override public int getSize() { int result = 0; RedisConnection connection = null; try { connection = jedisConnectionFactory.getConnection(); result = Integer.valueOf(connection.dbSize().toString()); } catch (JedisConnectionException e) { e.printStackTrace(); } finally { if (connection != null) { connection.close(); } } return result; } @Override public void putObject(Object key, Object value) { RedisConnection connection = null; try { connection = jedisConnectionFactory.getConnection(); RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer(); connection.set(serializer.serialize(key), serializer.serialize(value)); } catch (JedisConnectionException e) { e.printStackTrace(); } finally { if (connection != null) { connection.close(); } } } @Override public Object removeObject(Object key) { RedisConnection connection = null; Object result = null; try { connection = jedisConnectionFactory.getConnection(); RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer(); result = connection.expire(serializer.serialize(key), 0); } catch (JedisConnectionException e) { e.printStackTrace(); } finally { if (connection != null) { connection.close(); } } return result; } public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) { MybatisRedisCache.jedisConnectionFactory = jedisConnectionFactory; } }
注意:
可以看到,这个类并不是由Spring虚拟机管理的类,但是,其中有一个静态属性jedisConnectionFactory需要注入一个Spring bean,也就是在RedisConfig中生成的bean。
在一个普通类中使用Spring虚拟机管理的Bean,一般使用Springboot自省的SpringContextAware。
这里使用了另一种方式,静态注入的方式。这个方式是通过RedisCacheTransfer来实现的。
4、静态注入
RedisCacheTransfer.java
可以看到RedisCacheTransfer是一个springboot bean,在容器创建之初进行初始化的时候,会注入jedisConnectionFactory bean给setJedisConnectionFactory方法的传参。
而setJedisConnectionFactory通过调用静态方法设置了类MybatisRedisCache的静态属性jedisConnectionFactory。
这样就把spring容器管理的jedisConnectionFactory注入到了静态域。
@Component public class RedisCacheTransfer { @Autowired public void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) { MybatisRedisCache.setJedisConnectionFactory(jedisConnectionFactory); } }
到这里,代码基本已经搞定,下面是一些配置。主要有(1)全局开关;(2)namespace作用域开关;(3)Model实例序列化。
5、Mybatis二级缓存的全局开关
前面提到过,默认二级缓存没有打开,需要设置为true。这是全局二级缓存的开关。
Mybatis的全局配置。
<?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="cacheEnabled" value="true"/> </settings> </configuration>
全局配置的加载在dataSource中可以是这样的
@Bean(name = "moonlightSqlSessionFactory") @Primary public SqlSessionFactory moonlightSqlSessionFactory(@Qualifier("moonlightData") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis-mapper/*.xml")); bean.setConfigLocation(new ClassPathResource("mybatis-config.xml")); return bean.getObject(); }
6、配置mapper作用域namespace
前面提到过,二级缓存的作用域是mapper的namespace,所以这个配置需要到mapper中去写。
<mapper namespace="com.kangaroo.studio.moonlight.dao.mapper.MoonlightMapper"> <cache type="com.kangaroo.studio.moonlight.dao.cache.MybatisRedisCache"/> <resultMap id="geoFenceList" type="com.kangaroo.studio.moonlight.dao.model.GeoFence"> <constructor> <idArg column="id" javaType="java.lang.Integer" jdbcType="INTEGER" /> <arg column="name" javaType="java.lang.String" jdbcType="VARCHAR" /> <arg column="type" javaType="java.lang.Integer" jdbcType="INTEGER" /> <arg column="group" javaType="java.lang.String" jdbcType="VARCHAR" /> <arg column="geo" javaType="java.lang.String" jdbcType="VARCHAR" /> <arg column="createTime" javaType="java.lang.String" jdbcType="VARCHAR" /> <arg column="updateTime" javaType="java.lang.String" jdbcType="VARCHAR" /> </constructor> </resultMap> <select id="queryGeoFence" parameterType="com.kangaroo.studio.moonlight.dao.model.GeoFenceQueryParam" resultMap="geoFenceList"> select <include refid="base_column"/> from geoFence where 1=1 <if test="type != null"> and type = #{type} </if> <if test="name != null"> and name like concat('%', #{name},'%') </if> <if test="group != null"> and `group` like concat('%', #{group},'%') </if> <if test="startTime != null"> and createTime >= #{startTime} </if> <if test="endTime != null"> and createTime <= #{endTime} </if> </select> </mapper>
注意:
namespace下的cache标签就是加载缓存的配置,缓存使用的正式我们刚才实现的MybatisRedisCache。
<cache type="com.kangaroo.studio.moonlight.dao.cache.MybatisRedisCache"/>
7、Mapper和Model
读写缓存Model需要序列化:只需要类声明的时候实现Seriaziable接口就好了。
public class GeoFence implements Serializable { private Integer id; private String name; private String group; private Integer type; private String geo; private String createTime; private String updateTime; public GeoFence() {} public GeoFence(Integer id, String name, Integer type, String group, String geo, String createTime, String updateTime) { this.id = id; this.name = name; this.type = type; this.group = group; this.geo= geo; this.createTime = createTime; this.updateTime = updateTime; } // setter和getter省略 }
mapper就还是以前的写法
@Mapper public interface MoonlightMapper { List<GeoFence> queryGeoFence(GeoFenceQueryParam geoFenceQueryParam); }
到这里,所有的代码和配置都完成了,下面测试一下。
8、测试一下
Controller中实现一个这样的接口POST。
@RequestMapping(value = "/fence/query", method = RequestMethod.POST) @ResponseBody public ResponseEntity<Response> queryFence(@RequestBody GeoFenceQueryParam geoFenceQueryParam) { try { Integer pageNum = geoFenceQueryParam.getPageNum()!=null?geoFenceQueryParam.getPageNum():1; Integer pageSize = geoFenceQueryParam.getPageSize()!=null?geoFenceQueryParam.getPageSize():10; PageHelper.startPage(pageNum, pageSize); List<GeoFence> list = moonlightMapper.queryGeoFence(geoFenceQueryParam); return new ResponseEntity<>( new Response(ResultCode.SUCCESS, "查询geoFence成功", list), HttpStatus.OK); } catch (Exception e) { logger.error("查询geoFence失败", e); return new ResponseEntity<>( new Response(ResultCode.EXCEPTION, "查询geoFence失败", null), HttpStatus.INTERNAL_SERVER_ERROR); }
使用curl发送请求
curl -H "Content-Type:application/json" -XPOST http://bigdata.xiaojukeji.com/data_monitor/moonlight/fence/query -d '{ "name" : "test", "group": "test", , "startTime":"2017-12-06 00:00:00", "endTime":"2017-12-06 16:00:00", , }'
请求了三次,日志打印如下,
可以看到,只有第一次执行了sql模板查询,后面都是命中了缓存。
完毕。
Springboot Mybatis Redis 实现二级缓存的更多相关文章
- Spring + MySQL + Mybatis + Redis【二级缓存】
一.Redis环境 Redis 官网 :http://redis.io/ windows下载:https://github.com/dmajkic/redis/downloads 1.文件解压缩 2. ...
- springboot+mybatis+redis实现分布式缓存
大家都知道springboot项目都是微服务部署,A服务和B服务分开部署,那么它们如何更新或者获取共有模块的缓存数据,或者给A服务做分布式集群负载,如何确保A服务的所有集群都能同步公共模块的缓存数据, ...
- mybatis+redis实现二级缓存
在网上看了很多资料,发现例子都是千篇一律的相互复制.而且,使用的都是jedis的客户端..我这里使用的是redistemplate类实现. 缓存的原理..实现cache类接口,当哪个类需要缓存的时候, ...
- Spring + MySQL + Mybatis + Redis【二级缓存】执行流程分析
一级缓存基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就 ...
- mybatis plus使用redis作为二级缓存
建议缓存放到 service 层,你可以自定义自己的 BaseServiceImpl 重写注解父类方法,继承自己的实现.为了方便,这里我们将缓存放到mapper层.mybatis-plus整合redi ...
- SpringMVC + MyBatis + Mysql + Redis(作为二级缓存) 配置
2016年03月03日 10:37:47 标签: mysql / redis / mybatis / spring mvc / spring 33805 项目环境: 在SpringMVC + MyBa ...
- mybatis 使用redis实现二级缓存(spring boot)
mybatis 自定义redis做二级缓存 前言 如果关注功能实现,可以直接看功能实现部分 何时使用二级缓存 一个宗旨---不常变的稳定而常用的 一级是默认开启的sqlsession级别的. 只在单表 ...
- SpringMVC +Spring + MyBatis + Mysql + Redis(作为二级缓存) 配置
转载:http://blog.csdn.net/xiadi934/article/details/50786293 项目环境: 在SpringMVC +Spring + MyBatis + MySQL ...
- Mybatis的二级缓存、使用Redis做二级缓存
目录 什么是二级缓存? 1. 开启二级缓存 如何使用二级缓存: userCache和flushCache 2. 使用Redis实现二级缓存 如何使用 3. Redis二级缓存源码分析 什么是二级缓存? ...
随机推荐
- Java企业微信开发_Exception_02_java.security.InvalidKeyException: Illegal key size
今天换了重新装了一个jdk,然后运行昨天还好好的企业微信工程,结果启动的时候就给我报了这么个错: java.security.InvalidKeyException: Illegal key size ...
- OpenCV Image Watch 调试插件
昨晚偶然发现vs2012的这个很神奇的插件,对于经常使用opencv的人来说,这个插件无疑是我们的调试神器.今天马上下载试用,感觉超级棒!~以后要想查看图像结果,不用再imshow了! Image W ...
- THINKPHP中几个缓存的问题
1.字段缓存. THINKPHP是默认开启字段缓存的.如果关闭了APPDEBUG(即在index.php中设置了这样一句话:define("APP_DEBUG","FAL ...
- Intelligence System
Intelligence System Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...
- Equations
Equations Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Sub ...
- The Euler function(线性筛欧拉函数)
/* 题意:(n)表示小于n与n互质的数有多少个,给你两个数a,b让你计算a+(a+1)+(a+2)+......+b; 初步思路:暴力搞一下,打表 #放弃:打了十几分钟没打完 #改进:欧拉函数:具体 ...
- PHP基础入门(二)【PHP函数基础】
PHP基础入门(二)--函数基础 了解 PHP基础入门详解(一) 后,给大家分享一下PHP的函数基础. 这部分主要讲的就是: 函数的声明与使用.PHP中变量的作用域.静态变量.函数的参数传递.变量函数 ...
- 常见的XSS攻击代码
第一类: <tag on*=*/> 在html标签事件中触发,典型的是on*事件,但是这种触发模式的缺陷在于不能直接触发所以更多的需要配合使用. eg: 1.使html元素占据整个显示页面 ...
- Java并发Fork-Join框架原理解析
1.什么是Foirk/Join框架 Fork/Join框架是Java7提供用于并行执行任务的框架,是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架. 2.什么是并行流与顺 ...
- C# Dapper 轻量ORM调试对SQLServer
Dapper简介 Dapper只有一个代码文件,完全开源,你可以放在项目里的任何位置,来实现数据到对象的ORM操作,体积小速度快. 使用ORM的好处是增.删.改很快,不用自己写sql,因为这都是重复技 ...