SpringBoot+Mybatis整合Redis做MySQL的二級緩存
第一次接觸Redis有說的不對的地方歡迎大家指正,代碼親測可以食用。文章是在vscode中用markdown插件寫完的,後來想想還是發在csdn上吧,就直接站題過來了,排版或者什麼可能會有些問題,就先說這麼多叭。
文章目錄
1. 什麼是Redis
- Redis是一個NoSQL key-value型數據庫,在項目中使用Redis主要考慮性能和併發兩個方面。redis還可以做簡單的消息中間件,分佈式鎖等
2. 緩存機制
- 一級緩存和二級緩存
Mybatis默認開啓一級緩存,MyBatis框架定義了一個Cache接口來支持二級(第三方)緩存。
3. 整合
3.1 在application.yml中配置mysql、druid連接池(不配也能用)、服務器端口號
spring:
profiles:
active: prod
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/db_nenu_market?autoReconnect=true&useSSL=false
username: root
password: xy111225
type: com.alibaba.druid.pool.DruidDataSource
# 下面爲連接池的補充設置,應用到上面所有數據源中
# 初始化大小,最小,最大
initialSize: 1
minIdle: 3
maxActive: 20
# 配置獲取連接等待超時的時間
maxWait: 60000
# 配置間隔多久才進行一次檢測,檢測需要關閉的空閒連接,單位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一個連接在池中最小生存的時間,單位是毫秒
minEvictableIdleTimeMillis: 30000
validationQuery: select 'x'
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打開PSCache,並且指定每個連接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
# 配置監控統計攔截的filters,去掉後監控界面sql無法統計,'wall'用於防火牆
filters: stat,wall,slf4j
# 通過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合併多個DruidDataSource的監控數據
useGlobalDataSourceStat: true
server:
port:8080
servlet:
path"/springboot
- 開啓MyBatis二級緩存、配置相關路徑
mybatis:
configuration:
cache-enabled: true
#實體類所做包
type-aliases-package: com.join.nenuscfx.entity
#mapper.xml所在位置
mapper-locations: classpath:mapper/*/*.xml
- 配置Redis
redis:
host: 127.0.0.1
port: 6379
timeout: 5000
database: 0
#我的redis沒有密碼
password:
3.2 實現cache接口
- Cache接口實際上定義了你希望MyBatis在增刪改查時進行操作的規則
所以我們需要編寫一個類來實現這個接口,定義這些規則
RedisCache.class
import com.join.nenuscfx.util.ApplicationContextHolder;
import org.apache.ibatis.cache.Cache;
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.data.redis.core.ValueOperations;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class RedisCache implements Cache {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final String id;
private RedisTemplate redisTemplate;
//redis過期時間
private static final long EXPIRE_TIME_IN_MINUTES = 30;
public RedisCache(String id){
if (id == null){
throw new IllegalArgumentException("Cache instance required an ID");
}
this.id = id;
}
@Override
public String getId() {
return id;
}
/**
* Put query result to redis
* @Param key
* @Param value
* */
@Override
public void putObject(Object key, Object value) {
RedisTemplate redisTemplate = getRedisTemplate();
ValueOperations opsForValue = redisTemplate.opsForValue();
System.out.println(key+": key");
System.out.println(key.toString()+": key.toString()");
System.out.println(value+": value");
System.out.println(value.toString()+": value.toString()");
opsForValue.set(key.toString(),value,EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
System.out.println("結果成功放入緩存 and "+"key = " +"\n"+ key + "value = " + value);
System.out.println(opsForValue.get(key.toString()));
}
/**
* Get cached query result to redis
* @Param key
* @Return
* */
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
// redisTemplate.setHashValueSerializer(new StringRedisSerializer());
ValueOperations opsForValue = redisTemplate.opsForValue();
System.out.println("結果從緩存中獲取");
return opsForValue.get(key.toString());
}
/**
* Remove cached query result to redis
* @Param key
* @Return
* */
@Override
public Object removeObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(key);
System.out.println("從緩存中刪除");
return null;
}
/**
* Clear this cache instance
* */
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.execute((RedisCallback) connection -> {
connection.flushDb();
return null;
});
System.out.println("清空緩存");
}
@Override
public int getSize() {
Long size = (Long) redisTemplate.execute((RedisCallback) connection -> connection.dbSize());
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if(redisTemplate == null){
redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
}
return redisTemplate;
}
}
- 由於redisTemplate無法通過註解交給spring容器託管,所以我們需要手動注入。創建ApplicationContextHolder類來完成這個功能。
ApplicationContextHolder
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class ApplicationContextHolder implements ApplicationContextAware, DisposableBean {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = applicationContext;
}
public static ApplicationContext getApplicationContext(){
checkApplicationContext();
return applicationContext;
}
public static <T> T getBean(String name){
checkApplicationContext();
return (T) applicationContext.getBean(name);
}
public static <T> T getBean(Class<T> clazz){
checkApplicationContext();
return (T) applicationContext.getBeansOfType(clazz);
}
public static void cleanApplicationContext(){
applicationContext = null;
}
private static void checkApplicationContext(){
if(applicationContext ==null){
if (applicationContext == null){
throw new IllegalStateException("applicationContext未注入,請在applicationContext.xml中定義SpringContext");
}
}
}
@Override
public void destroy() throws Exception {
}
}
- 注意一下,當我們的數據存儲到Redis的時候,我們的key和value都是通過Spring提供的Serializer序列化到redis數據庫的,RedisTemplate默認使用的是JdkSerializationRedisSerializer,StringRedisTemplate默認使用的是StringRedisSerializer。如果想要自己定義序列化方式,可以自己配製RedisTemplate並定義Serializer。這裏提供一個通過Jackson2JsonRedisSerializer來序列化value以及通過StringRedisSerializer來序列化key的示例代碼。
既然是使用序列化方式,那麼實體類一定要實現Serializable接口,請自己自行在實體類後寫上 implements Serializable
注:這段代碼是在網上博客上找到的
package com.join.nenuscfx.redis;
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;
@Configuration
public class RedisConfig {
/**
* 重寫Redis序列化方式,使用Json方式:
* 當我們的數據存儲到Redis的時候,我們的鍵(key)和值(value)都是通過Spring提供的Serializer序列化到數據庫的。RedisTemplate默認使用的是JdkSerializationRedisSerializer,StringRedisTemplate默認使用的是StringRedisSerializer。
* Spring Data JPA爲我們提供了下面的Serializer:
* GenericToStringSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、OxmSerializer、StringRedisSerializer。
* 在此我們將自己配置RedisTemplate並定義Serializer。
*
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(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;
}
}
3.3 在mapper.xml文件中指定使用RedisCache作爲緩存
<mapper namespace="com.join.nenuscfx.mapper.usermapper.UserMapper">
<cache type="com.join.nenuscfx.redis.RedisCache"/>
</mapper>
4 最後想說的話以及踩過的坑
- 序列化在我看來就是將對象以字節序列形式存儲,反序列化就是從字節序列中直接獲得一個對象。
- 在get(key)時如果不是寫成get(key.toString())會報:CacheKey can’t cast to java.lang.String
- 我在剛寫好這個項目時,出現錯誤:
無法反序列化,非法的反序列化header 000000
具體的錯誤信息忘記了,但翻譯過來基本是這個意思。找了很多博客都沒實際解決問題,有說序列化與反序列化方式不一致的,有說不能使用jdk自帶的序列化的,最後好像還是key.toString()的問題。當時也不知道怎麼就謎一樣的解決掉了,以後遇到再說吧。Over。