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二级缓存源码分析 什么是二级缓存? ...
随机推荐
- sublime text3 开发必备插件
1,Package Control 通俗易懂地说,这个是你在完成安装SublimeText后必须安装的东西.你问为什么?因为有了这个特殊的"插件包",你可以很容易地安装.升级.删除 ...
- 用css属性画出一棵圣诞树
对于学习前端的童鞋,css的掌握是必须的.今天就来实现用css画出一棵圣诞树. 主要练习的是css里面border的练习与掌握程度. 在body创建一个主区域<div></div&g ...
- JS框架设计读书笔记之-小知识
这一篇写一点小知识 JS中0.1+0.2为什么不等于0.3? 关于这个问题之前也很疑虑,老师也只是笼统的讲这是JS的语言问题,但是内部具体的情况却没有讲,看了书才发现原理如此简单. 简单来讲,计算机识 ...
- 版本12.2.0.1.0数据库,复制种子数据库快速创建租户数据库PDB
实验测试:快速创建一个数据库PDB2: 实验环境:12.2.0.1.0版本数据库,dbca图形化安装,现有环境,CDB容器数据库ORCL,PDB可插拔数据库ABC ---查询CDB名称,状态 SQ ...
- Java项目打包方式分析
[TOC] 概述 在项目实践过程中,有个需求需要做一个引擎能执行指定jar包的指定main方法. 起初我们以一个简单的spring-boot项目进行测试,使用spring-boot-maven-plu ...
- 【译】Java中的字符串字面量
原文地址:https://javaranch.com/journal/200409/ScjpTipLine-StringsLiterally.html 作者:Corey McGlone 让我们由一个简 ...
- java项目log4j日志打印配置
#定义输出级别和输出平台 添加DEBUG表示打印sql 语句 log4j.rootLogger=DEBUG,INFO,ERROR,stdout,R log4j.category.org.spring ...
- 利用VS2008发布一个简单的webservice
一个开发好的webservice,怎样发布出去,供其他电脑访问呢? 本文将介绍如何发布一个简单的webservice,其中的内容都是在网上查看别人文章,自己仿照着做了一遍,因此,难免会发生错误,如果发 ...
- 小明滚出---响应对象HttpServletResponse和请求对象HttpServletRequest实例
<Servlet类的Java> @WebServlet("/studentServlet") public class StudentServlet extends H ...
- Android 开发笔记___实战项目:购物车
购物车的应用很广泛,电商app基本上都有它的身影.由于它用到了多种存储方式,通过项目对数据的存储有更高层次的了解. 1.设计思路 首先看看购物车的外观.第一次进入时里面是空的,去购物页面加入购物车以后 ...