SpringCache
为什么要学习SpringCache,他解决了什么问题?
SpringCache是Spring3.1版本发布的,他是对使用缓存进行封装和抽象,通过在方法上使用annotation注解就能拿到缓存结果,正式因为用了annotation,所以它解决了业务代码和缓存代码的耦合度问题,即再不入侵业务代码的基础上让现有代码即刻支持缓存,让开发者无感知redis的存在
(对于redis缓存,springcache只支持string.其他的Hash,List,Set,ZSet都不支持)
开始
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--springboot的redis支持-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
#服务配置
server:
#端口
port: 8081
servlet:
#项目路径
context-path: /platform
#服务编码
tomcat:
uri-encoding: UTF-8
#spring相关配置
spring:
#应用配置
application:
#应用名称
name: springboot-day01-basic-project
#数据源配置
datasource:
#选择druid数据源
druid:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring-boot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: root
redis:
host: 127.0.0.1 #Redis服务器地址
port: 6379#Redis服务器连接端口
timeout: 2000 #请求redis服务的超时时间,这里注意设置成0时取默认时间2000
jedis: #阻塞的
pool:
#连接池最大连接数(使用负值表示没有限制)
#建议为业务期望QPS/一个连接的QPS,例如50000/1000=50
#一次命令时间(borrow|return resource+Jedis执行命令+网络延迟)的平均耗时约为1ms,一个连接的QPS大约是1000
max-active: 50
#连接池中的最大空闲连接
#建议和最大连接数一致,这样做的好处是连接数从不减少,从而避免了连接池伸缩产生的性能开销。
max-idle: 50
#连接池中的最小空闲连接
#建议为0,在无请求的状况下从不创建链接
min-idle: 0
#连接池最大阻塞等待时间 毫秒(-1表示没有限制)
#建议不要为-1,连接池占满后无法获取连接时将在该时间内阻塞等待,超时后将抛出异常。
max-wait: 2000
#mubatis配置
mybatis-plus:
# MyBaits 别名包扫描路径,通过该属性可以给包中的类注册别名
type-aliases-package: com.springboot.pojo
# 该配置请和 typeAliasesPackage 一起使用,如果配置了该属性,则仅仅会扫描路径下以该类作为父类的域对象 。
type-aliases-super-type: com.springboot.basic.BasicPojo
configuration:
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# 驼峰下划线转换
map-underscore-to-camel-case: true
use-generated-keys: true
default-statement-timeout: 60
default-fetch-size: 100
global-config:
db-config:
#主键类型(雪花ID)
id-type: assign_id
#机器 ID 部分(影响雪花ID)
worker-id: 1
#数据标识 ID 部分(影响雪花ID)
datacenter-id: 1
logging:
config: classpath:logback.xml
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.itheima.springboot.serializer.KryoRedisSerializer;
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.*;
import java.time.Duration;
/**
* @ClassName RedisCacheConfig.java
* @Description redis配置
*/
@Configuration
@EnableCaching
public class RedisCacheConfig {
/**
* 申明缓存管理器,会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut)
* 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// KryoRedisSerializer 替换默认序列化
// KryoRedisSerializer kryoRedisSerializer = new KryoRedisSerializer(Object.class);
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(60))
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(genericJackson2JsonRedisSerializer))
.disableCachingNullValues();
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(config)
.build();
}
}
- @Cacheable:触发缓存写入。
- @CacheEvict:触发缓存清除。
- @CachePut:更新缓存(不会影响到方法的运行)。
- @Caching:重新组合要应用于方法的多个缓存操作
- @CacheConfig:设置类级别上共享的一些常见缓存设置
@Cacheable注解
如果缓存中没有:查询数据库,存储缓存,返回结果,如果缓存中有:直接返回结果
作用:可以用来进行缓存的写入,将结果存储在缓存中,以便于在后续调用的时候可以直接返回缓存中的值,而不必再执行实际的方法。 最简单的使用方式,注解名称=缓存名称,使用例子如下:
@CacheEvict注解
@CacheEvict:删除缓存的注解,这对删除旧的数据和无用的数据是非常有用的。这里还多了一个参数(allEntries),设置allEntries=true时,可以对整个条目进行批量删除
@CachePut注解
@CachePut:当需要更新缓存而不干扰方法的运行时 ,可以使用该注解。也就是说,始终执行该方法,并将结果放入缓存
@Caching注释
在使用缓存的时候,有可能会同时进行更新和删除,会出现同时使用多个注解的情况.而@Caching可以实现
//添加user缓存的同时,移除userPage的缓存
@Caching(put =@CachePut(value = "user",key ="#userVo.id"),
evict = @CacheEvict(value = "userPage",allEntries = true))
@CacheConfig注解
缓存提供了许多的注解选项,但是有一些公用的操作,我们可以使用@CacheConfig在类上进行全局设置。 以下是个简单的例子
@CacheConfig("books")
public class BookRepositoryImpl implements BookRepository {
@Cacheable
public Book findBook(ISBN isbn) {...}
}
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.itheima.springboot.basic.ResponseWrap;
import com.itheima.springboot.enums.StatusEnum;
import com.itheima.springboot.exception.ProjectException;
import com.itheima.springboot.pojo.User;
import com.itheima.springboot.service.IUserService;
import com.itheima.springboot.utils.BeanConv;
import com.itheima.springboot.utils.EmptyUtil;
import com.itheima.springboot.utils.ExceptionsUtil;
import com.itheima.springboot.vo.UserVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
/**
* @Description:前端控制器
*/
@RestController
@RequestMapping("/springboot/user")
@Api(tags = "用户操作")
@Log4j2
@CrossOrigin
public class UserController {
@Autowired
IUserService userService;
/***
* @description 注册用户
* @param userVo 注册信息
* @return: java.lang.Boolean
*/
@PostMapping
@ApiOperation(value = "用户注册",notes = "用户注册")
@ApiImplicitParam(name = "userVo",value = "注册信息",required = true,dataType = "UserVo")
@Caching(put =@CachePut(value = "user",key ="#result.data.id"),evict = @CacheEvict(value = "userPage",allEntries = true))
public ResponseWrap<UserVo> saveUser(@RequestBody UserVo userVo) throws ProjectException {
try {
User user = BeanConv.toBean(userVo, User.class);
userService.save(user);
BeanConv.toBean(user, userVo);
return ResponseWrap.<UserVo>builder()
.code(StatusEnum.SUCCEED.getCode())
.msg(StatusEnum.SUCCEED.getMsg())
.operationTime(new Date())
.data(userVo)
.build();
}catch (Exception e){
log.error("用户注册:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StatusEnum.REGISTER_USER_FAIL.getCode(),
StatusEnum.REGISTER_USER_FAIL.getMsg());
}
}
/***
* @description 删除用户
* @param userId 用户Id
* @return: java.lang.Boolean
*/
@DeleteMapping("{userId}")
@ApiOperation(value = "删除用户",notes = "删除用户")
@ApiImplicitParam(name = "userId",value = "删除用户",required = true,dataType = "Long")
@Caching(evict = {@CacheEvict(value = "user",key ="#userId"),@CacheEvict(value = "userPage",allEntries = true)})
public ResponseWrap<Boolean> deleteUser(@PathVariable("userId") Long userId) throws ProjectException {
try {
Boolean flag = userService.removeById(userId);
return ResponseWrap.<Boolean>builder()
.code(StatusEnum.SUCCEED.getCode())
.msg(StatusEnum.SUCCEED.getMsg())
.operationTime(new Date())
.data(flag)
.build();
}catch (Exception e){
log.error("删除用户:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StatusEnum.DELETE_USER_FAIL.getCode(),
StatusEnum.DELETE_USER_FAIL.getMsg());
}
}
/***
* @description 编辑用户
* @param userVo 注册信息
* @return: java.lang.Boolean
*/
@PutMapping
@ApiOperation(value = "编辑用户",notes = "编辑用户")
@ApiImplicitParam(name = "userVo",value = "编辑用户",required = true,dataType = "UserVo")
@Caching(put =@CachePut(value = "user",key ="#userVo.id"),evict = @CacheEvict(value = "userPage",allEntries = true))
public ResponseWrap<Boolean> updateUser(@RequestBody UserVo userVo) throws ProjectException {
try {
Boolean flag = userService.saveOrUpdate(BeanConv.toBean(userVo, User.class));
return ResponseWrap.<Boolean>builder()
.code(StatusEnum.SUCCEED.getCode())
.msg(StatusEnum.SUCCEED.getMsg())
.operationTime(new Date())
.data(flag)
.build();
}catch (Exception e){
log.error("查询用户:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StatusEnum.UPDATE_USER_FAIL.getCode(),
StatusEnum.UPDATE_USER_FAIL.getMsg());
}
}
/***
* @description 查询用户
* @param userId 用户Id
* @return: java.lang.Boolean
*/
@GetMapping("/{userId}")
@ApiOperation(value = "查询用户",notes = "查询用户")
@ApiImplicitParam(name = "userId",value = "查询用户",required = true,dataType = "Long")
@Cacheable(value = "user",key ="#userId")
public ResponseWrap<UserVo> findUserById(@PathVariable("userId") Long userId) throws ProjectException {
try {
User user = userService.getById(userId);
return ResponseWrap.<UserVo>builder()
.code(StatusEnum.SUCCEED.getCode())
.msg(StatusEnum.SUCCEED.getMsg())
.operationTime(new Date())
.data(BeanConv.toBean(user,UserVo.class))
.build();
}catch (Exception e){
log.error("查询用户:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StatusEnum.FIND_USER_FAIL.getCode(),
StatusEnum.FIND_USER_FAIL.getMsg());
}
}
/***
* @description 分页查询用户
* @param current 当前页面
* @param size 每页条数
* @return: java.lang.Boolean
*/
@PostMapping("{current}/{size}")
@ApiOperation(value = "分页查询用户",notes = "查询用户")
@ApiImplicitParams({
@ApiImplicitParam(name = "current",value = "当前页码",required = true,dataType = "Long"),
@ApiImplicitParam(name = "size",value = "每页条数",required = true,dataType = "Long"),
@ApiImplicitParam(name = "userVo",value = "用户条件",required = true,dataType = "UserVo")
})
@Cacheable(value = "userPage",key ="'current_'+#current+'_userVo_'+#userVo.hashCode()")
public ResponseWrap<Page<UserVo>> findUserPage(@PathVariable("current") Long current,
@PathVariable("size") Long size,
@RequestBody UserVo userVo) throws ProjectException {
try {
//构建分页
Page<User> page = new Page<>(current,size);
//查询条件
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
if (!EmptyUtil.isNullOrEmpty(userVo.getLoginName())) {
queryWrapper.likeRight(StringUtils.camelToUnderline(User.Fields.loginName),userVo.getLoginName());
}
if (!EmptyUtil.isNullOrEmpty(userVo.getMobil())) {
queryWrapper.likeRight(StringUtils.camelToUnderline(User.Fields.mobil),userVo.getMobil());
}
if (!EmptyUtil.isNullOrEmpty(userVo.getEnableFlag())) {
queryWrapper.eq(StringUtils.camelToUnderline(User.Fields.enableFlag),userVo.getEnableFlag());
}
//执行查询
Page<User> userPageResult = userService.page(page, queryWrapper);
Page<UserVo> userVoPageResult = new Page<>();
BeanConv.toBean(userPageResult,userVoPageResult);
userVoPageResult.setRecords(BeanConv.toBeanList(userPageResult.getRecords(),UserVo.class));
return ResponseWrap.<Page<UserVo>>builder()
.code(StatusEnum.SUCCEED.getCode())
.msg(StatusEnum.SUCCEED.getMsg())
.operationTime(new Date())
.data(userVoPageResult)
.build();
}catch (Exception e){
log.error("用户注册:{}", ExceptionsUtil.getStackTraceAsString(e));
throw new ProjectException(StatusEnum.FIND_USER_FAIL.getCode(),
StatusEnum.FIND_USER_FAIL.getMsg());
}
}
}