我們都知道使用redis
來快取我們的資料集合,如下圖所示。
通常自己去快取資料,這樣的優點就是邏輯清晰,而且redis
的key
和value
會比較規範。但是冗餘程式碼會比較多,需要自己進行判斷資料是否過期。
為了簡化業務程式碼,現在用註解的方式整合redis
二級快取,但是他的key
和value
就會比較不符合規範。他的key
一共包含5個部分,最重要的就是sql
和這個sql
的引數。他的value
就是這個查詢的結果集。
引入依賴,mybatis
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<exclusions>
<!-- 排除 tomcat-jdbc 以使用 HikariCP -->
<exclusion>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.6</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-extension</artifactId>
<version>3.0.6</version>
</dependency>
redis
依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
組態檔
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
# 這裡使用的是 ip:3336/db_order 的資料庫(一個服務一個資料庫)
url: jdbc:mysql://localhost:3306/sys-common?useUnicode=true&characterEncoding=utf-8&serverTimezone=Hongkong&useSSL=false
username: root
password: 123456
hikari:
minimum-idle: 5
idle-timeout: 600000
maximum-pool-size: 10
auto-commit: true
pool-name: MyHikariCP
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
# redis
redis:
host: xxx.xxx.xxx.xxx
port: 6379
lettuce:
pool:
max-active: 8
max-idle: 8
max-wait: -1ms
min-idle: 0
database: 4
設定redisTemplate
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* ProjectName: hello-redis-cache
* Package: com.laoshiren.hello.redis.cache.mybatis.configure
* ClassName: RedisConfiguration
* Author: laoshiren
* Description:
* Date: 2020/9/13 15:49
* Version: 1.0
*/
@Configuration
public class RedisConfiguration {
/**
* 設定redisTemplate
*/
@Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用String 序列化
// redisTemplate.setDefaultSerializer(new StringRedisSerializer());
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
注意這裡我不使用String
的序列化方式去序列化Key
和Value
實現Cache
介面
package com.laoshiren.hello.redis.cache.mybatis.cache;
import com.laoshiren.hello.redis.cache.mybatis.configure.ApplicationContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ProjectName: hello-redis-cache
* Package: com.laoshiren.hello.redis.cache.mybatis.cache
* ClassName: RedisCache
* Author: laoshiren
* Description:
* Date: 2020/9/13 15:34
* Version: 1.0
*/
@Slf4j
public class RedisCache implements Cache {
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final String id; // cache instance id
private RedisTemplate redisTemplate;
private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis過期時間
public RedisCache(String id) {
if (id == null) {
throw new IllegalArgumentException("Cache instances require 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) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.opsForValue().set(key, value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
log.debug("Put query result to redis");
} catch (Throwable t) {
log.error("Redis put failed", t);
}
}
/**
* Get cached query result from redis
*
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
log.info("Get cached query result from redis");
// System.out.println("****" + opsForValue.get(key).toString());
return redisTemplate.opsForValue().get(key);
} catch (Throwable t) {
log.error("Redis get failed, fail over to db", t);
return null;
}
}
/**
* Remove cached query result from redis
*
* @param key
* @return
*/
@Override
@SuppressWarnings("unchecked")
public Object removeObject(Object key) {
try {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete( key.toString());
log.debug("Remove cached query result from redis");
} catch (Throwable t) {
log.error("Redis remove failed", t);
}
return null;
}
/**
* Clears this cache instance
*/
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.execute((RedisCallback) connection -> {
connection.flushDb();
return null;
});
log.debug("Clear all the cached query result from redis");
}
/**
* This method is not used
*
* @return
*/
@Override
public int getSize() {
return 0;
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate getRedisTemplate() {
if (redisTemplate == null) {
redisTemplate = ApplicationContextHolder.getBean("redisTemplate");
}
return redisTemplate;
}
}
給指定的mapper
設定快取
@CacheNamespace(implementation = RedisCache.class)
public interface TbPostMapper extends BaseMapper<TbPost> {
}
請求一次資料庫,使用Debug
模式,它的key
是一個CacheKey
,無法使用使用StringRedisSerializer
去序列化,所以redisTemplate
得使用預設的序列化,即 JdkSerializationRedisSerializer
開啟RDM
,看一下4號庫。
發現key
和value
就不是很美觀,不過不影響使用,當然您可以使用StringRedisSerializer
去實現,只不過我在嘗試的過程中獲取sql
和引數的時候,會出現一點問題。希望有大佬可以指出。
特別注意! 在分頁快取的時候,Page
物件的total
必須自己手動查詢一次,不然返回給前端的物件裡第一次還有總頁數,第二次由於走了快取就不帶這個total
,所以必須手動查詢一次。
@GetMapping("page/{pageNo}/{pageSize}")
public ResponseResult<IPage<Area>> page(@PathVariable(name = "pageNo")Integer pageNo,
@PathVariable(name = "pageSize") Integer pageSize,
HttpServletRequest request){
IPage<Area> wherePage = new Page<>(pageNo, pageSize);
String word = request.getParameter("wd");
LambdaQueryWrapper<Area> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(word)) {
queryWrapper.like(Area::getAreaName,word);
}
int count = areaService.count(queryWrapper);
IPage<Area> page = areaService.page(wherePage,queryWrapper);
page.setTotal((long)count);
return new ResponseResult<>(CodeStatus.OK,"操作成功",page);
}
後續我也會繼續更新這篇部落格。好了,最後還是借用大佬的一句話:「不經一番寒徹骨,怎知梅花撲鼻香」。