Springboot + Mybatis + Redis
Mybatis的二级缓存是多个SqlSession共享的,作用于是mapper配置文件中同一个namespace,不同的SqlSession两次执行相同namespace下的sql语句且参数如果也一样则最终执行的sql语句是相同的。每次查询都会先看看缓存中是否有对应查询结果,如果有就从缓存拿,如果没有就执行sql语句从数据库中读取,从而提高查询效率。Mybatis默认开启的是一级缓存,所以二级缓存需要自己手动开启。
ps: 本项目是基于springboot + mybatis 环境下配置Redis
环境
- 开发环境:Window 10
- IDE: Intellij 2017.2
- JDK: 1.8
- Redis:3.2.100
- Oracle:12.1.0.2.0
application.properties
开启二级缓存
12345
#Mybatismybatis.mapper-locations=classpath:com/sunnada/hurd/*/dao/*.xmlmybatis.type-aliases-package=com.sunnada.hurd#开启MyBatis的二级缓存mybatis.configuration.cache-enabled=true
#Mybatis
mybatis.mapper-locations=classpath:com/sunnada/hurd//dao/.xml
mybatis.type-aliases-package=com.sunnada.hurd
#开启MyBatis的二级缓存
mybatis.configuration.cache-enabled=true
配置redis
123456789
#redis#database namespring.redis.database=0#server hostspring.redis.host=192.168.168.9#server passwordspring.redis.password=#connection portspring.redis.port=6379
#redis
#database name
spring.redis.database=0
#server host
spring.redis.host=192.168.168.9
#server password
spring.redis.password=
#connection port
spring.redis.port=6379
pom.xml
添加redis依赖
1234
dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId/dependency
dependency
groupIdorg.springframework.boot/groupId
artifactIdspring-boot-starter-data-redis/artifactId
/dependency
RedisConfig
这里Redis配置类重写了Redis序列化的方式,改用Json的数据结构传输数据。
配置RedisTemplate并定义Serializer方式。
12345678910111213141516171819202122232425262728293031323334353637383940414243
package com.sunnada.hurd.config; import com.fasterxml.jackson.annotation.JsonAutoDetect;import com.fasterxml.jackson.annotation.PropertyAccessor;import com.fasterxml.jackson.databind.ObjectMapper;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.connection.RedisConnectionFactory;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;import org.springframework.data.redis.serializer.StringRedisSerializer; /** * @program: HurdProject * @description: Redis配置类 * @author: linyh * @create: 2018-09-10 17:17 **/@Configurationpublic class RedisConfig { @Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplateString, Object redisTemplate = new RedisTemplate(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializerObject jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializerObject(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); // 设置值(value)的序列化采用Jackson2JsonRedisSerializer。 redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // 设置键(key)的序列化采用StringRedisSerializer。 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.afterPropertiesSet(); return redisTemplate; } }
package com.sunnada.hurd.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
- @program: HurdProject
- @description: Redis配置类
- @author: linyh
- @create: 2018-09-10 17:17
**/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplateString, Object redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplateString, Object redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializerObject jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializerObject(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// 设置值(value)的序列化采用Jackson2JsonRedisSerializer。
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
// 设置键(key)的序列化采用StringRedisSerializer。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
SpringContextHolder
组件,实现了Spring的ApplicationContextAware来获取ApplicationContext,从中获取容器的bean
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
package com.sunnada.hurd.cache; import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.stereotype.Component; /** * @description: 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候中取出ApplicaitonContext * @author: linyh * @create: 2018-09-10 17:25 **/@Componentpublic class SpringContextHolder implements ApplicationContextAware{ private static ApplicationContext applicationContext; /** * 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量. */ public void setApplicationContext(ApplicationContext applicationContext) { SpringContextHolder.applicationContext = applicationContext; // NOSONAR } /** * 取得存储在静态变量中的ApplicationContext. */ public static ApplicationContext getApplicationContext() { checkApplicationContext(); return applicationContext; } /** * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型. */ @SuppressWarnings("unchecked") public static T T getBean(String name) { checkApplicationContext(); return (T) applicationContext.getBean(name); } /** * 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型. */ @SuppressWarnings("unchecked") public static T T getBean(ClassT clazz) { checkApplicationContext(); return (T) applicationContext.getBeansOfType(clazz); } /** * 清除applicationContext静态变量. */ public static void cleanApplicationContext() { applicationContext = null; } private static void checkApplicationContext() { if (applicationContext == null) { throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder"); } }}
package com.sunnada.hurd.cache;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
- @description: 以静态变量保存Spring ApplicationContext, 可在任何代码任何地方任何时候中取出ApplicaitonContext
- @author: linyh
- @create: 2018-09-10 17:25
**/
@Component
public class SpringContextHolder implements ApplicationContextAware{
private static ApplicationContext applicationContext;
/**
* 实现ApplicationContextAware接口的context注入函数, 将其存入静态变量.
*/
public void setApplicationContext(ApplicationContext applicationContext) {
SpringContextHolder.applicationContext = applicationContext; // NOSONAR
}
/**
* 取得存储在静态变量中的ApplicationContext.
*/
public static ApplicationContext getApplicationContext() {
checkApplicationContext();
return applicationContext;
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static T getBean(String name) {
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
/**
* 从静态变量ApplicationContext中取得Bean, 自动转型为所赋值对象的类型.
*/
@SuppressWarnings("unchecked")
public static T getBean(Class clazz) {
checkApplicationContext();
return (T) applicationContext.getBeansOfType(clazz);
}
/**
* 清除applicationContext静态变量.
*/
public static void cleanApplicationContext() {
applicationContext = null;
}
private static void checkApplicationContext() {
if (applicationContext == null) {
throw new IllegalStateException("applicaitonContext未注入,请在applicationContext.xml中定义SpringContextHolder");
}
}
}
MybatisRedisCache
Mybatis二级缓存默认使用的是其他的缓存,这里我们需要集成Redis就需要自己自定义写一个缓存类去实现二级缓存。
自定义缓存需要实现Mybatis的Cache接口。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
package com.sunnada.hurd.cache; import org.apache.ibatis.cache.Cache;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.dao.DataAccessException;import org.springframework.data.redis.connection.RedisConnection;import org.springframework.data.redis.core.RedisCallback;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.util.CollectionUtils; import java.util.Set;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock; /** * @description: 使用Redis实现Mybatis二级缓存,实现Cache接口 * @author: linyh * @create: 2018-09-10 17:21 **/public class MybatisRedisCache implements Cache { //private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class); // 读写锁 private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true); private RedisTemplateString, Object redisTemplate = SpringContextHolder.getBean("redisTemplate"); private String id; public MybatisRedisCache(final String id) { if (id == null) { throw new IllegalArgumentException("Cache instances require an ID"); } //logger.info("Redis Cache id " + id); this.id = id; } @Override public String getId() { return this.id; } @Override public void putObject(Object key, Object value) { if (value != null) { // 向Redis中添加数据,有效时间是2天 redisTemplate.opsForValue().set(key.toString(), value, 2, TimeUnit.DAYS); } } @Override public Object getObject(Object key) { try { if (key != null) { Object obj = redisTemplate.opsForValue().get(key.toString()); return obj; } } catch (Exception e) { //logger.error("redis "); } return null; } @Override public Object removeObject(Object key) { try { if (key != null) { redisTemplate.delete(key.toString()); } } catch (Exception e) { } return null; } @Override public void clear() { //logger.debug("清空缓存"); try { SetString keys = redisTemplate.keys("*:" + this.id + "*"); if (!CollectionUtils.isEmpty(keys)) { redisTemplate.delete(keys); } } catch (Exception e) { } } @Override public int getSize() { Long size = (Long) redisTemplate.execute(new RedisCallbackLong() { @Override public Long doInRedis(RedisConnection connection) throws DataAccessException { return connection.dbSize(); } }); return size.intValue(); } @Override public ReadWriteLock getReadWriteLock() { return this.readWriteLock; }}
package com.sunnada.hurd.cache;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
- @description: 使用Redis实现Mybatis二级缓存,实现Cache接口
- @author: linyh
- @create: 2018-09-10 17:21
**/
public class MybatisRedisCache implements Cache {
//private static final Logger logger = LoggerFactory.getLogger(MybatisRedisCache.class);
// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
private RedisTemplateString, Object redisTemplate = SpringContextHolder.getBean("redisTemplate");
private String id;
public MybatisRedisCache(final String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require an ID");
}
//logger.info("Redis Cache id " + id);
this.id = id;
}
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
if (value != null) {
// 向Redis中添加数据,有效时间是2天
redisTemplate.opsForValue().set(key.toString(), value, 2, TimeUnit.DAYS);
}
}
@Override
public Object getObject(Object key) {
try {
if (key != null) {
Object obj = redisTemplate.opsForValue().get(key.toString());
return obj;
}
} catch (Exception e) {
//logger.error("redis ");
}
return null;
}
@Override
public Object removeObject(Object key) {
try {
if (key != null) {
redisTemplate.delete(key.toString());
}
} catch (Exception e) {
}
return null;
}
@Override
public void clear() {
//logger.debug("清空缓存");
try {
SetString keys = redisTemplate.keys("*:" + this.id + "*");
if (!CollectionUtils.isEmpty(keys)) {
redisTemplate.delete(keys);
}
} catch (Exception e) {
}
}
@Override
public int getSize() {
Long size = (Long) redisTemplate.execute(new RedisCallbackLong() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.dbSize();
}
});
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return this.readWriteLock;
}
}
Mapper.xml
mapper映射配置文件,只需要引入刚刚配置好的自定义缓存类。
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
?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.sunnada.hurd.dictionary.dao.CertificationTypeMapper" !--resultMap id="DemoResultMap" type="com.sunnada.hurd.demo.pojo.Demo" id column="id" jdbcType="INT" property="id" / result column="name" jdbcType="VARCHAR" property="name" / /resultMap-- cache type="com.sunnada.hurd.cache.MybatisRedisCache" property name="eviction" value="LRU" / property name="flushInterval" value="6000000" / property name="size" value="1024" / property name="readOnly" value="false" / /cache insert id="insert" parameterType="CertificationType" insert into DIS_CERTIFICATION_TYPE ( ID,NAME,CODE,PARENT_ID,SORT,STATUS,CREATE_TIME,MODIFY_TIME,OLD_SYSTEM_ID ) values (${id},#{name},#{code},#{parentID},#{sort},#{status},#{createTime},#{modifiedTime},#{oldSystemID}) selectKey keyProperty="id" order="BEFORE" resultType="int" SELECT DIS_CERTIFICATION_TYPE_ID_SEQ.NEXTVAL FROM dual /selectKey /insert delete id="delete" parameterType="java.lang.Integer" delete from DIS_CERTIFICATION_TYPE where ID= #{id} /delete select id="get" parameterType="_int" resultType="CertificationType" select * from DIS_CERTIFICATION_TYPE where ID= #{id} and STATUS = 1 /select update id="update" parameterType="CertificationType" update DIS_CERTIFICATION_TYPE set if test="name != null and name.length() 0"NAME=#{name},/if if test="code != null and code.length() 0"CODE=#{code},/if if test="sort != 0"SORT=#{sort},/if if test="createTime != null"CREATE_TIME=#{createTime},/if if test="modifiedTime != null"MODIFY_TIME=#{modifiedTime},/if STATUS=#{status} /set where ID=#{id} /update select id="list" parameterType="CertificationType" resultType="CertificationType" select * from DIS_CERTIFICATION_TYPE where if test="name != null and name.length() 0" bind name="likename" value="'%'+ name +'%'"/bind and NAME like #{likename} /if and STATUS = 1 /where /select/mapper
?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.sunnada.hurd.dictionary.dao.CertificationTypeMapper”
!–resultMap id=”DemoResultMap” type=”com.sunnada.hurd.demo.pojo.Demo”
id column=”id” jdbcType=”INT” property=”id” /
result column=”name” jdbcType=”VARCHAR” property=”name” /
/resultMap–
cache type="com.sunnada.hurd.cache.MybatisRedisCache"
property name="eviction" value="LRU" /
property name="flushInterval" value="6000000" /
property name="size" value="1024" /
property name="readOnly" value="false" /
/cache
insert id="insert" parameterType="CertificationType"
insert into DIS_CERTIFICATION_TYPE ( ID,NAME,CODE,PARENT_ID,SORT,STATUS,CREATE_TIME,MODIFY_TIME,OLD_SYSTEM_ID )
values (${id},#{name},#{code},#{parentID},#{sort},#{status},#{createTime},#{modifiedTime},#{oldSystemID})
selectKey keyProperty="id" order="BEFORE" resultType="int"
SELECT DIS_CERTIFICATION_TYPE_ID_SEQ.NEXTVAL FROM dual
/selectKey
/insert
delete id="delete" parameterType="java.lang.Integer"
delete from DIS_CERTIFICATION_TYPE where ID= #{id}
/delete
select id="get" parameterType="_int" resultType="CertificationType"
select * from DIS_CERTIFICATION_TYPE where ID= #{id} and STATUS = 1
/select
update id="update" parameterType="CertificationType"
update DIS_CERTIFICATION_TYPE
set
if test="name != null and name.length() 0"NAME=#{name},/if
if test="code != null and code.length() 0"CODE=#{code},/if
if test="sort != 0"SORT=#{sort},/if
if test="createTime != null"CREATE_TIME=#{createTime},/if
if test="modifiedTime != null"MODIFY_TIME=#{modifiedTime},/if
STATUS=#{status}
/set
where ID=#{id}
/update
select id="list" parameterType="CertificationType" resultType="CertificationType"
select * from DIS_CERTIFICATION_TYPE
where
if test="name != null and name.length() 0"
bind name="likename" value="'%'+ name +'%'"/bind
and NAME like #{likename}
/if
and STATUS = 1
/where
/select
/mapper
cache标签内属性:
eviction:定义缓存移除机制(算法),默认为LRU(最近最少使用),它会清除最少使用的数据,还有一种FIFO(先进先出),它会清除最先进来的数据。
flushInterval:定义缓存刷新周期,单位为毫秒。
size:标识缓存cache中容纳的最大元素,默认为1024。
readOnly:默认为false,可配置为true缓存只读。
(虽然我的配置大部分都为默认值,但个人观点写出来的话看上去会更清楚一点,所以都写上吧)
对于有不需要用到二级缓存的语句可以在标签内写userCache=”false”,默认为true开启缓存。
123
select id="get" parameterType="_int" resultType="CertificationType" useCache="false" select * from DIS_CERTIFICATION_TYPE where ID= #{id} and STATUS = 1/select
select id=”get” parameterType=”_int” resultType=”CertificationType” useCache=”false”
select * from DIS_CERTIFICATION_TYPE where ID= #{id} and STATUS = 1
/select
(select 默认useCache为true:使用缓存,flushCache为false:不清空缓存)
(insert、update、delete 默认flushCache为true:清空缓存)
其他的Mapper接口,Service类都照常编写即可
实体类
实体类需要实现Serializable
12345678910111213141516171819202122232425262728293031323334
package com.sunnada.hurd.dictionary.entity; import java.io.Serializable;import java.util.Date; /** * @Author:linyh * @Date: 2018/9/6 14:34 * @Modified By: */public class CertificationType implements Serializable{ private static final long serialVersionUID = 1L; private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
package com.sunnada.hurd.dictionary.entity;
import java.io.Serializable;
import java.util.Date;
/**
- @Author:linyh
- @Date: 2018/9/6 14:34
- @Modified By:
*/
public class CertificationType implements Serializable{
private static final long serialVersionUID = 1L;
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试
这里使用Postman进行测试。
访问url 获取列表 (第一次)
(第二次)
速度明显提升。