开启mybatis二级缓存-使用redis存储缓存

This commit is contained in:
brian 2023-07-27 18:36:25 +08:00
parent f0f0b6e3c4
commit 4f38f3be63
13 changed files with 230 additions and 44 deletions

View File

@ -126,7 +126,7 @@ yarn run dev
- [x] 引入Guava RateLimiter(单机) 和 Redisson RateLimiter(分布式) 两种限流机制
- [x] 支持用户对失败的图表进行手动重试
- [ ] 图表数据分表存储,提高查询灵活性和性能
- [ ] 引入redis缓存提高加载速度
- [x] 引入redis缓存提高加载速度
- [ ] 给任务执行增加 guava Retrying重试机制保证系统可靠性
- [ ] 定时任务把失败状态的图表放到队列中(补偿机制)
- [ ] 给任务的执行增加超时时间,超时自动标记为失败(超时控制)

View File

@ -17,10 +17,6 @@
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>

View File

@ -1,9 +1,7 @@
package top.peng.answerbi;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.scheduling.annotation.EnableScheduling;
@ -13,9 +11,7 @@ import org.springframework.scheduling.annotation.EnableScheduling;
* @author yunpeng
* @version 1.0 2023/5/16
*/
// todo 如需开启 Redis须移除 exclude 中的内容
@SpringBootApplication(exclude = {RedisAutoConfiguration.class})
@MapperScan("top.peng.answerbi.mapper")
@SpringBootApplication
@EnableScheduling
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class MainApplication {

View File

@ -66,7 +66,7 @@ public class BiMqConfig {
.to(biExchange())
.with(BiMqConstant.BI_ROUTING_KEY);
}
//绑定Bi分析业务队列到Bi分析业务交换机
//绑定死信队列到死信交换机
@Bean
public Binding DeadLetterBinding(){
return BindingBuilder

View File

@ -14,7 +14,7 @@ import org.springframework.context.annotation.Configuration;
* @version 1.0 2023/5/16
*/
@Configuration
@MapperScan("top.peng.springbootinit.mapper")
@MapperScan("top.peng.answerbi.mapper")
public class MyBatisPlusConfig {
/**

View File

@ -0,0 +1,79 @@
/*
* @(#)RedisTemplateConfig.java
*
* Copyright © 2023 YunPeng Corporation.
*/
package top.peng.answerbi.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import java.time.Duration;
import java.util.Objects;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* RedisTemplateConfig
*
* @author yunpeng
* @version 1.0 2023/7/27
*/
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置key和value的序列化规则
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
//afterPropertiesSet和init-method之间的执行顺序是afterPropertiesSet 先执行init - method 后执行
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisTemplate<Object,Object> redisTemplate) {
//基本配置
RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
//设置 key 为String
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer()))
//设置 value 为自动转Json 的Object
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()))
//不缓存 null
.disableCachingNullValues()
//配置缓存失效时间30分钟
.entryTtl(Duration.ofMinutes(30));
//构造一个redis缓存管理器
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(Objects.requireNonNull(redisTemplate.getConnectionFactory()))
.cacheDefaults(defaultCacheConfig)
//配置同步修改或删除
.transactionAware()
.build();
}
}

View File

@ -0,0 +1,111 @@
/*
* @(#)MyBatisRedisCache.java
*
* Copyright © 2023 YunPeng Corporation.
*/
package top.peng.answerbi.manager;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.connection.RedisServerCommands;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.CollectionUtils;
import top.peng.answerbi.utils.SpringContextUtils;
/**
* MyBatisRedisCache mybaits 缓存工具类
*
* @author yunpeng
* @version 1.0 2023/7/27
*/
@Slf4j
public class MybatisRedisCacheManager implements Cache {
private RedisTemplate<Object,Object> redisTemplate;
// 读写锁
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
// cache instance id
private final String id;
private static final long EXPIRE_TIME_IN_MINUTES = 30; // redis过期时间
public MybatisRedisCacheManager(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) {
getRedisTemplate().opsForValue().set(key.toString(), value, EXPIRE_TIME_IN_MINUTES, TimeUnit.MINUTES);
log.info("Put query result to redis, key={}",key);
}
@Override
public Object getObject(Object key) {
try {
log.info("Get cached query result from redis, key={}",key);
return getRedisTemplate().opsForValue().get(key.toString());
} catch (Exception e) {
log.error("Get cached query result from redis failed , key={}",key);
e.printStackTrace();
}
return null;
}
@Override
public Object removeObject(Object key) {
if (key != null){
getRedisTemplate().delete(key.toString());
log.info("Remove cached query result from redis, key={}",key);
}
return null;
}
@Override
public void clear() {
Set<Object> keys = getRedisTemplate().keys("*:" + this.id + "*");
if (!CollectionUtils.isEmpty(keys)) {
getRedisTemplate().delete(keys);
}
log.info("Clear all the cached query result from redis");
}
@Override
public int getSize() {
Long size = getRedisTemplate().execute(RedisServerCommands::dbSize);
if (size == null) return 0;
return size.intValue();
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
private RedisTemplate<Object,Object> getRedisTemplate(){
//通过SpringContextUtils工具类获取RedisTemplate
if (redisTemplate == null) {
redisTemplate = (RedisTemplate<Object,Object>) SpringContextUtils.getBean("redisTemplate");
}
return redisTemplate;
}
}

View File

@ -1,5 +1,7 @@
package top.peng.answerbi.mapper;
import org.apache.ibatis.annotations.CacheNamespace;
import top.peng.answerbi.manager.MybatisRedisCacheManager;
import top.peng.answerbi.model.entity.Chart;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@ -7,8 +9,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
* @author yunpeng.zhang
* @description 针对表chart(图表信息表)的数据库操作Mapper
* @createDate 2023-07-10 16:45:42
* @Entity top.peng.springbootinit.model.entity.Chart
* @Entity top.peng.answerbi.model.entity.Chart
*/
@CacheNamespace(implementation = MybatisRedisCacheManager.class)
public interface ChartMapper extends BaseMapper<Chart> {
}

View File

@ -1,14 +1,17 @@
package top.peng.answerbi.mapper;
import top.peng.answerbi.model.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.CacheNamespace;
import top.peng.answerbi.manager.MybatisRedisCacheManager;
import top.peng.answerbi.model.entity.User;
/**
* @author yunpeng.zhang
* @description 针对表user(用户)的数据库操作Mapper
* @createDate 2023-07-10 16:45:42
* @Entity top.peng.springbootinit.model.entity.User
* @Entity top.peng.answerbi.model.entity.User
*/
@CacheNamespace(implementation = MybatisRedisCacheManager.class)
public interface UserMapper extends BaseMapper<User> {
}

View File

@ -3,17 +3,6 @@ package top.peng.answerbi.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import top.peng.answerbi.common.ErrorCode;
import top.peng.answerbi.constant.CommonConstant;
import top.peng.answerbi.exception.BusinessException;
import top.peng.answerbi.mapper.UserMapper;
import top.peng.answerbi.model.dto.user.UserQueryRequest;
import top.peng.answerbi.model.entity.User;
import top.peng.answerbi.model.enums.UserRoleEnum;
import top.peng.answerbi.model.vo.LoginUserVO;
import top.peng.answerbi.model.vo.UserVO;
import top.peng.answerbi.service.UserService;
import top.peng.answerbi.utils.SqlUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@ -23,7 +12,18 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import top.peng.answerbi.common.ErrorCode;
import top.peng.answerbi.constant.CommonConstant;
import top.peng.answerbi.constant.UserConstant;
import top.peng.answerbi.exception.BusinessException;
import top.peng.answerbi.mapper.UserMapper;
import top.peng.answerbi.model.dto.user.UserQueryRequest;
import top.peng.answerbi.model.entity.User;
import top.peng.answerbi.model.enums.UserRoleEnum;
import top.peng.answerbi.model.vo.LoginUserVO;
import top.peng.answerbi.model.vo.UserVO;
import top.peng.answerbi.service.UserService;
import top.peng.answerbi.utils.SqlUtils;
/**
* 用户服务实现
@ -60,7 +60,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
// 账户不能重复
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_account", userAccount);
long count = this.baseMapper.selectCount(queryWrapper);
long count = this.count(queryWrapper);
if (count > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
}
@ -96,7 +96,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_account", userAccount);
queryWrapper.eq("user_password", encryptPassword);
User user = this.baseMapper.selectOne(queryWrapper);
User user = this.getOne(queryWrapper);
// 用户不存在
if (user == null) {
log.info("user login failed, userAccount cannot match userPassword");

View File

@ -16,9 +16,9 @@ spring:
port: 6379
timeout: 5000
password: 123456
# Elasticsearch 配置
# todo 需替换配置
elasticsearch:
uris: http://localhost:9200
username: root
password: 123456
# rabbitMq 配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

View File

@ -2,26 +2,24 @@ server:
port: 8101
spring:
# 数据库配置
# todo 需替换配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/answer_bi
username: root
password: 123456
# Redis 配置
# todo 需替换配置
redis:
database: 1
host: localhost
port: 6379
timeout: 5000
password: 123456
# Elasticsearch 配置
# todo 需替换配置
elasticsearch:
uris: http://localhost:9200
username: root
password: 123456
# rabbitMq 配置
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
mybatis-plus:
configuration:
# 生产环境关闭日志

View File

@ -10,8 +10,7 @@ spring:
matching-strategy: ant_path_matcher
# session 配置
session:
# todo 取消注释开启分布式 session须先配置 Redis
# store-type: redis
store-type: redis
# 30 天过期
timeout: 2592000
# 数据库配置
@ -51,6 +50,7 @@ mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
cache-enabled: true
global-config:
db-config:
logic-delete-field: deleted_flag # 全局逻辑删除的实体字段名