赞
踩
实际开发中,针对请求量大的数据并且频繁请求的数据信息,在不断的对数据库mysql、oracle等操作下,造成额外多的性能开销,同时获取信息会很慢。
为了减少频繁查询,造成响应时间大、服务器压力大的问题,可以采取设置缓存。
SpringCache
是 Spring 3.1
版本发布出来的,对使用缓存进行了封装和抽象,通过在方法上使用annotation
注解就能拿到缓存信息。
对于Redis缓存,SpringCache只支持String
类型,其他Hash、List、Set、ZSet都不支持。
查找、删除、修改某项数据后,Redis缓存中的对应数据也做变更。
之前不采取配置方式,只类似如下逻辑方式进行操作(伪代码
):
@RequestMapping("/xxxx)
User getUser(String uid){
// 查询mysql
// 向redis中写入相关数据
// 回执
}
此时采取配置方式实现数据的缓存保存,避免额外的代码量。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- <version>5.0.4</version> -->
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>swagger-bootstrap-ui</artifactId>
<version>1.9.6</version>
</dependency>
由于使用Spring 自带的cache,将查询到的数据信息保存至redis中,此处需要做两类配置:
redis保存对象、json等信息的编码配置类。
cache保存缓存信息等配置类。
server:
port: 80
# 配置redis以及cache
spring:
redis:
host: 192.168.99.100
port: 10000
password: xiangjiao
timeout: 10000 #连接超时时间
#database: 1
jedis: ## jedis配置
pool: ## 连接池配置
max-idle: 8 ## 最大空闲连接数
max-active: 8 ## 最大连接数
max-wait: 3000 ## 最大阻塞等待时间
min-idle: 0 ## 最小空闲连接数
datasource:
url: jdbc:mysql://192.168.99.100:3306/redis_cache?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
profiles.active: dev
## mapper 配置
logging.level.cn.linkpower.dao: debug
## mybatis 配置
mybatis:
## 驼峰命名匹配
configuration:
map-underscore-to-camel-case: true
type-aliases-package: cn.linkpower.dao
## 扫描mapper文件
mapper-locations:
- classpath:mapper/*.xml
import org.springframework.cache.CacheManager;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching // 开启缓存
public class RedisConfig {
/**
* 重写Redis序列化定义方式,采取json方式————避免json格式乱码
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
// 创建json序列化对象
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 设置key序列化String
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置value序列化 json
redisTemplate.setValueSerializer(genericJackson2JsonRedisSerializer);
// 设置hash key序列化String
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 设置hash value 序列化json
redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
// 初始化redis完成序列化的方法
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 缓存配置
* @param factory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration cacheConfiguration =
RedisCacheConfiguration.defaultCacheConfig()
//设置缓存默认超时时间 30分钟
//.entryTtl(Duration.ofMillis(30))
.disableCachingNullValues()
// 设置key序列化
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2 //启动Swagger
public class SwaggerConfig {
/**
* swagger 核心类
* @return
*/
@Bean
public Docket createRestApi1(Environment environment) {
// 适用环境
Profiles profiles = Profiles.of("dev", "test");
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.groupName("AAA")
.apiInfo(apiInfo())
.enable(flag)//enable是否启动swagger,如果为false,则Swagger不能在浏览器中访问
.select()
//RequestHandlerSelectors, 配置要扫描接口的方式
//basePackage()扫描全部
//any()扫描全部
//none()不扫描
//withClassAnnotation()扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("cn.linkpower"))
//path() 过滤什么路径
.paths(PathSelectors.any())
.build();
}
/**
* 分组显示
* @return
*/
@Bean
public Docket createRestApi2(Environment environment) {
// 适用环境
Profiles profiles = Profiles.of("dev", "test");
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.groupName("BBB")
.apiInfo(apiInfo())
.enable(flag)//enable是否启动swagger,如果为false,则Swagger不能在浏览器中访问
.select()
//RequestHandlerSelectors, 配置要扫描接口的方式
//basePackage()扫描全部
//any()扫描全部
//none()不扫描
//withClassAnnotation()扫描方法上的注解
.apis(RequestHandlerSelectors.basePackage("cn.linkpower"))
//path() 过滤什么路径
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
// 标题
.title("专注写bug , swagger测试")
// 创建人基础信息
.contact(new Contact("专注写bugs", "localhost:80", "123@qq.com"))
// 版本信息
.version("1.0.0")
.description("swagger 测试代码")
.build();
}
}
CREATE TABLE `user` (
`id` int(32) NOT NULL AUTO_INCREMENT,
`userName` varchar(32) NOT NULL,
`passWord` varchar(50) NOT NULL,
`createTimes` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.linkpower.service.UserService;
import cn.linkpower.vo.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
@RestController
@Api("用户测试类")
public class UserController {
Logger log = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userServiceImpl;
@GetMapping("/getuser/{id}")
@ApiOperation(value = "获取用户信息",notes = "queryUser")
@ApiImplicitParam(name = "id", value = "用户id", paramType = "path", required = true, dataType = "String")
public User queryUser(@PathVariable(value = "id") String id) {
log.info("----queryUser---id---{}",String.valueOf(id));
return userServiceImpl.queryUserById(Integer.parseInt(id));
}
}
import cn.linkpower.vo.User;
public interface UserService {
/**
* 根据id查询用户信息
* @param id
* @return
*/
User queryUserById(Integer id);
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import cn.linkpower.dao.UserMapper;
import cn.linkpower.vo.User;
@Service
//@CacheConfig(cacheNames = {"user"}) //提取注解公共属性
public class UserServiceImpl implements UserService {
Logger log = LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
private UserMapper userMapper;
@Override
@Cacheable(value = "userCache", key = "#id")
public User queryUserById(Integer id) {
log.error("----查询数据库----");
return userMapper.queryUserById(id);
}
}
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("用户实体类")
public class User implements Serializable {
private static final long serialVersionUID = 3526560195700752585L;
@ApiModelProperty("用户主键")
private Integer id;
@ApiModelProperty("用户名")
private String userName;
@ApiModelProperty("用户密码")
private String passWord;
@ApiModelProperty("创建日期")
private Long createTimes;
@Override
public String toString() {
return "User [id=" + id + ", userName=" + userName + ", passWord=" + passWord + ", createTimes=" + createTimes
+ "]";
}
}
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import cn.linkpower.vo.User;
@Mapper
public interface UserMapper {
/**
* 根据id查询用户信息
* @param id
* @return
*/
@Select("select id,userName,passWord,createTimes from user where id = #{id}")
User queryUserById(Integer id);
}
此处配置了在线接口文档,就从接口文档中进行请求测试:
http://localhost/doc.html
请求接口链接,查看后台日志输出:
由于初始Redis中并未存在缓存信息,所以先会从数据库中查询信息并添加至Redis中,在之后的请求操作中,由于此时Redis中已存在指定的缓存信息,则直接从Redis中返回对应信息。
该注解用于需要进行缓存操作的方法(一般是service接口)
。其中包含上述的几个配置项。
String[] cacheNames() default {}
:缓存操作,指定数据对应的名称,可以多个。
String[] value() default {}
:
cacheNames
的别名。
String key() default ""
:Spring Expression Language(SpEL)表达式,用于动态计算密钥。
Spel 表达式默认提供了专用的上下文求值,比如:
#root.method
java.lang.reflect.Method方法、
#root.target
目标对象、
#root.caches
受影响的缓存,
#root.methodName
方法名、
#root.targetClass
目标类
修改业务代码,查看缓存数据保存格式:
@Autowired
private UserMapper userMapper;
@Override
@Cacheable( key = "#root.method")
public User queryUserById(Integer userId) {
log.error("----查询数据库----");
return userMapper.queryUserById(userId);
}
此处修改为#root.method
,请求接口,查看redis中保存的数据格式:
注意!!!
但是使用#root.method
、#root.target
、#root.caches
等会出现如下问题:
当参数信息变更,Redis缓存中的信息不会更改!!!导致缓存数据读取是错误的。
该项参数信息可以为空,如果编写必须按照spel
语法指定。其次也能按照接受参数指定:
@Override
@Cacheable( key = "#userId")
public User queryUserById(Integer userId) {
log.error("----查询数据库----");
return userMapper.queryUserById(userId);
}
必须保证key
中的名称值和方法中对应参数的名称保持一致,否则报错!
String condition() default ""
: @Override
@Cacheable( key = "#userId",condition = "#userId>1")
public User queryUserById2(Integer userId) {
log.error("----查询数据库----");
return userMapper.queryUserById(userId);
}
不满足条件查询:
满足条件查询:
String unless() default ""
:这项注解信息表示某个类中很多缓存操作时,名称的汇总。
@CacheConfig(cacheNames = {“user”}) //提取注解公共属性
表示缓存的更新操作。如下所示:
控制层:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.linkpower.service.UserService;
import cn.linkpower.vo.User;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
@RestController
@Api("用户测试类")
public class UserController {
Logger log = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userServiceImpl;
@GetMapping("/getuser/{id}")
@ApiOperation(value = "获取用户信息",notes = "queryUser")
@ApiImplicitParam(name = "id", value = "用户id", paramType = "path", required = true, dataType = "String")
public User queryUser(@PathVariable(value = "id") String id) {
log.info("----queryUser---id---{}",String.valueOf(id));
return userServiceImpl.queryUserById1(Integer.parseInt(id));
}
@PostMapping("/updateUser")
@ApiOperation(value = "修改用户信息",notes = "updateUser")
//@ApiImplicitParam(name = "user", value = "用户信息", paramType = "body", required = true, dataType = "cn.linkpower.vo.User")
public User updateUser( @RequestBody User user) {
return userServiceImpl.updateUser(user);
}
}
业务层:
@Override
@Cacheable( key = "#userId")
public User queryUserById1(Integer userId) {
log.error("----查询数据库----");
return userMapper.queryUserById(userId);
}
@Override
@CachePut(key = "#user.id")
public User updateUser(User user) {
log.error("----更新数据库----");
userMapper.updateUser(user);
log.info("id:{}",user.getId());
return userMapper.queryUserById(user.getId());
}
dao层:
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import cn.linkpower.vo.User;
@Mapper
public interface UserMapper {
/**
* 根据id查询用户信息
* @param id
* @return
*/
@Select("select id,userName,passWord,createTimes from user where id = #{id}")
User queryUserById(Integer id);
void updateUser(User user);
}
<update id="updateUser" parameterType="cn.linkpower.vo.User">
update user
<set>
<if test="userName != null">
userName = #{userName},
</if>
<if test="passWord != null">
passWord = #{passWord},
</if>
</set>
where 1 = 1
<if test="id != null">
and id = #{id}
</if>
</update>
使用
@Cacheable
注解信息,在初次请求更新操作时,修改数据库信息;
如果redis中也有对应信息,则一并修改。
再次请求查询接口,发现Redis中存在最新的记录信息,此时直接从Redis中读取缓存。
该注解用于删除Redis中对应的缓存信息。
写一个demo,例如:
控制类:
@GetMapping("/deluser/{id}")
@ApiOperation(value = "删除用户信息",notes = "delUser")
@ApiImplicitParam(name = "id", value = "用户id", paramType = "path", required = true, dataType = "String")
public User delUser(@PathVariable(value = "id") Integer id) {
log.info("----queryUser---id---{}",String.valueOf(id));
userServiceImpl.delUserById1(id);
return null;
}
业务实现类:
@Override
@CacheEvict(key = "#id")
public void delUserById1(Integer id) {
userMapper.delUserById1(id);
}
dao接口:
@Delete("delete from user where id = #{id}")
void delUserById1(Integer id);
请求之前,Redis数据库和mysql数据库数据信息:
请求测试:
此时的redis和mysql数据库信息:
此项注解用于包含多种类型的缓存操作。
比如,有时候的业务需求,需要同时删除某些数据,更新某些数据。
命令 | 说明 |
---|---|
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager | 缓存管理器,管理各种缓存(cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。与@Cacheable区别在于是否每次都调用方法,常用于更新 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
@CacheConfig | 统一配置本类的缓存注解的属性 |
名称 | 说明 |
---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写, 如果不指定,则缺省按照方法的所有参数进行组合 例如:@Cacheable(value=”testcache”,key=”#id”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false, 只有为 true 才进行缓存/清除缓存 例如:@Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
unless | 否定缓存。当条件结果为TRUE时,就不会缓存。 @Cacheable(value=”testcache”,unless=”#userName.length()>2”) |
allEntries (@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指定为 true, 则方法调用后将立即清空所有缓存 例如: @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation (@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果指定为 true, 则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法 执行抛出异常,则不会清空缓存 例如:@CachEvict(value=”testcache”,beforeInvocation=true) |
名称 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root对象 | 当前被调用的方法名 | #root.methodname |
method | root对象 | 当前被调用的方法 | #root.method.name |
target | root对象 | 当前被调用的目标对象实例 | #root.target |
targetClass | root对象 | 当前被调用的目标对象的类 | #root.targetClass |
args | root对象 | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root对象 | 当前方法调用使用的缓存列表 | #root.caches[0].name |
Argument Name | 执行上下文 | 当前被调用的方法的参数,如findArtisan(Artisan artisan),可以通过#artsian.id获得参数 | #artsian.id |
result | 执行上下文 | 方法执行后的返回值(仅当方法执行后的判断有效,如 unless cacheEvict的beforeInvocation=false) | #result |
注意:
1.当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性。 如
@Cacheable(key = “targetClass + methodName +#p0”)
2.使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。 如:
@Cacheable(value=“users”, key="#id")
@Cacheable(value=“users”, key="#p0")
类型 | 运算符 |
---|---|
关系 | <,>,<=,>=,==,!=,lt,gt,le,ge,eq,ne |
算术 | +,- ,* ,/,%,^ |
逻辑 | &&,|| ,!,and,or,not,between,instanceof |
条件 | ?: (ternary),?: (elvis) |
正则表达式 | matches |
其他类型 | ?.,?[…],![…],^[…] ,$[…] |
springboot整合spring @Cache和Redis
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。