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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ import org.springframework.context.annotation.Configuration;
* @version 1.0 2023/5/16 * @version 1.0 2023/5/16
*/ */
@Configuration @Configuration
@MapperScan("top.peng.springbootinit.mapper") @MapperScan("top.peng.answerbi.mapper")
public class MyBatisPlusConfig { 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; 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 top.peng.answerbi.model.entity.Chart;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
@ -7,8 +9,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
* @author yunpeng.zhang * @author yunpeng.zhang
* @description 针对表chart(图表信息表)的数据库操作Mapper * @description 针对表chart(图表信息表)的数据库操作Mapper
* @createDate 2023-07-10 16:45:42 * @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> { public interface ChartMapper extends BaseMapper<Chart> {
} }

View File

@ -1,14 +1,17 @@
package top.peng.answerbi.mapper; package top.peng.answerbi.mapper;
import top.peng.answerbi.model.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; 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 * @author yunpeng.zhang
* @description 针对表user(用户)的数据库操作Mapper * @description 针对表user(用户)的数据库操作Mapper
* @createDate 2023-07-10 16:45:42 * @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> { 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.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -23,7 +12,18 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils; 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.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<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_account", userAccount); queryWrapper.eq("user_account", userAccount);
long count = this.baseMapper.selectCount(queryWrapper); long count = this.count(queryWrapper);
if (count > 0) { if (count > 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复"); 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<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("user_account", userAccount); queryWrapper.eq("user_account", userAccount);
queryWrapper.eq("user_password", encryptPassword); queryWrapper.eq("user_password", encryptPassword);
User user = this.baseMapper.selectOne(queryWrapper); User user = this.getOne(queryWrapper);
// 用户不存在 // 用户不存在
if (user == null) { if (user == null) {
log.info("user login failed, userAccount cannot match userPassword"); log.info("user login failed, userAccount cannot match userPassword");

View File

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

View File

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

View File

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