Redis 是一个高性能的key-value内存数据库。它支持常用的5种数据结构:String字符串、Hash哈希表、List列表、Set集合、Zset有序集合 等数据类型。
Redis它解决了2个问题:
1)性能
通常数据库的读操作,一般都要几十毫秒,而redisd的读操作一般仅需不到1毫秒。通常只要把数据库的数据缓存进redis,就能得到几十倍甚至上百倍的性能提升。
2)并发
在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常,甚至卡死在数据库中。为了解决大并发卡死的问题,一般的做法是采用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。
- 下面用Redis做缓存,整合SpringBoot+Mybatis来讲解。
1.2、添加Redis配置信息org.springframework.boot spring-boot-starter-parent 2.2.2.RELEASE com.xyl string_mybatis 0.0.1-SNAPSHOT string_mybatis Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test tk.mybatis mapper-spring-boot-starter 2.0.3 mysql mysql-connector-java io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 org.springframework.boot spring-boot-starter-redis 1.4.7.RELEASE org.springframework.boot spring-boot-maven-plugin
spring.application.name=spring-boot-mybatis-redis
server.port=9090
# 指定mapper.xml的位置
mybatis.mapper-locations=classpath*:com/xyl/redis/mapper/xml
private String username;
private String password;
private Byte sex;
private Byte deleted;
@Column(name = "update_time")
private Date updateTime;
@Column(name = "create_time")
private Date createTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Byte getSex() {
return sex;
}
public void setSex(Byte sex) {
this.sex = sex;
}
public Byte getDeleted() {
return deleted;
}
public void setDeleted(Byte deleted) {
this.deleted = deleted;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
", password='" + password + ''' +
", sex=" + sex +
'}';
}
}
- UserVO
package com.xyl.redis.controller;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Date;
@ApiModel(value = "用户信息")
public class UserVO {
@ApiModelProperty(value = "用户ID")
private Integer id;
@ApiModelProperty(value = "用户名")
private String username;
@ApiModelProperty(value = "密码")
private String password;
@ApiModelProperty(value = "性别 0=女 1=男 ")
private Byte sex;
@ApiModelProperty(value = "删除标志,默认0不删除,1删除")
private Byte deleted;
@ApiModelProperty(value = "更新时间")
private Date updateTime;
@ApiModelProperty(value = "创建时间")
private Date createTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Byte getSex() {
return sex;
}
public void setSex(Byte sex) {
this.sex = sex;
}
public Byte getDeleted() {
return deleted;
}
public void setDeleted(Byte deleted) {
this.deleted = deleted;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + ''' +
", password='" + password + ''' +
", sex=" + sex +
'}';
}
}
1.4、编写Mapper、Config配置类
- UserMapper.xml
- UserMapper
package com.xyl.redis.mapper; import com.xyl.redis.entity.User; import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper{ }
- 配置类SwaggerConfig :
package com.xyl.redis.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.documentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Value(value = "${spring.swagger2.enabled}")
private Boolean swaggerEnabled;
@Bean
public Docket createRestApi() {
return new Docket(documentationType.SWAGGER_2)
.apiInfo(apiInfo())
.enable(swaggerEnabled)
.select()
.apis(RequestHandlerSelectors.basePackage("com.xyl.redis"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("接口文档")
.description("SpringBoot+Redis学习总结")
.termsOfServiceUrl("https://www.bilibili.com/video/BV1GV411U78a?p=3")
.version("1.0")
.build();
}
}
1.5、编写业务处理层service
- UserService
package com.xyl.redis.service;
import com.xyl.redis.entity.User;
import com.xyl.redis.mapper.UserMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
@Service
public class UserService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
private static final String Cache_Key_User = "user:";
@Autowired
private UserMapper userMapper;
@Autowired
private RedisTemplate redisTemplate;
// 增加User,并更新到Redis
public void saveUser(User user){
// 向数据库中加入 user数据项
this.userMapper.insertSelective(user);
String key = Cache_Key_User + user.getId();
user = userMapper.selectByPrimaryKey(user.getId());
//opsForValue 对应 Redis的String数据结构, SET命令
// 将从数据库中查询到的数据 缓存到redis
redisTemplate.opsForValue().set(key,user);
}
// 修改User,并更新到Redis
public void updateUser(User user){
this.userMapper.updateByPrimaryKeySelective(user);
String key = Cache_Key_User + user.getId();
User redisUser = userMapper.selectByPrimaryKey(user.getId());
redisTemplate.opsForValue().set(key,redisUser);
}
public User findUserById(Integer userId){
ValueOperations operations = redisTemplate.opsForValue();
String key = Cache_Key_User + userId;
// 先根据userId 去Redis 查找User,如果Redis中没有数据,再去数据库查找,找到数据后,更新到Redis中
User user = operations.get(key);
if (user == null){
user = this.userMapper.selectByPrimaryKey(userId);
redisTemplate.opsForValue().set(key,user);
}
return user;
}
}
1.6、编写控制层UserController
package com.xyl.redis.controller;
import com.xyl.redis.entity.User;
import com.xyl.redis.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.annotation.RequestScope;
import java.beans.Beans;
import java.util.Random;
@Api(description = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@ApiOperation("数据库初始化100条数据")
@RequestMapping(value = "/init",method = RequestMethod.GET)
public void init(){
for (int i = 0; i < 100; i++) {
Random random = new Random();
User user = new User();
String userStr = "user" + i;
user.setUsername(userStr);
user.setPassword(userStr + "psd");
int gender = random.nextInt(2);
user.setSex((byte) gender);
userService.saveUser(user);
}
}
@ApiOperation("单个用户查询,按userid查用户信息")
@RequestMapping(value = "/findById/{id}", method = RequestMethod.GET)
public UserVO findById(@PathVariable Integer uid){
User user = this.userService.findUserById(uid);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user,userVO);
return userVO;
}
@ApiOperation("修改User数据")
@PostMapping("/updateUser")
public void updateUser(@RequestBody UserVO userVO){
User user = new User();
BeanUtils.copyProperties(userVO,user);
this.userService.updateUser(user);
}
}
1.7、主启动类(添加@MapperScan)
package com.xyl.redis;
import tk.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
// 指定要扫描 Mapper类的包 路径
@MapperScan("com.xyl.redis.mapper")
@SpringBootApplication
public class StringMybatisApplication {
public static void main(String[] args) {
SpringApplication.run(StringMybatisApplication.class, args);
}
}
注意:添加@MapperScan需要导入的包是
import tk.mybatis.spring.annotation.MapperScan;`
而非org.mybatis.spring.annotation.MapperScan。
/user/init
数据库初始化100条数据
启动项目后,发送请求:
http://localhost:9090/swagger-ui.html#
结果如下图:
选中"/user/init"请求栏中的 Try it out -> Execute,向服务器发送初始化数据的请求。
-
请求结果如下:
数据库中增加了100条初始化数据。 -
注意:存放在Redis中的数据必须序列化(实体类对象实现Serializable接口)
选中 “/user/findById/{id}“请求栏中的"Try it out”,输入User对象的Id,再"Execute”,第一次execute执行查询操作,会直接从先Redis中查找数据,如果查找不到,会再去数据库中查询数据,如果查询到数据,返回数据的同时,将数据存储到Redis中,以便在后续的查询中能够命中缓存(第一次查询id为1002的user,控制台会输出对应的sql语句,再去查询id为1002的user,则不会显示sql语句,即直接从Redis查询并返回数据)。
- 为什么要重写Redis序列化方式,改为Json呢?
因为RedisTemplate默认使用的是JdkSerializationRedisSerializer,会出现2个问题:
1)被序列化的对象必须实现Serializable接口
@Table(name = "users")
public class User implements Serializable { // ... }
问题解决: 1)添加Redis的配置类RedisConfig :2)被序列化的数据在Redis会出现乱码,导致value值可读性差。
package com.xyl.redis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
//创建一个json的序列化对象
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//设置value的序列化方式json
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置hash key序列化方式string
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//设置hash value的序列化方式json
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
2)实体类User不用再实现序列化接口Serializable
@Table(name = "users") // 指定对应的表名
public class User { //... }
3)flushdb清空Redis中的数据
- 避免Redis中之前存在的旧数据对添加RedisConfig配置类后 产生影响。
启动项目,发送请求: http://localhost:9090/swagger-ui.html#/,在"/user/init"请求栏中,“Try it out” ->"Execute"来发送请求,数据库和Redis中均会存储对应的数据,Redis中存储的数据不再是乱码,而是JSON格式的字符串。
在微信公众号里面的文章,每个用户阅读一遍文章,该篇文章的阅读量就会加一。类似于微信这样大的并发量,一般不可能采用数据库来做计数器,通常都是用redis的incr命令来实现。如下图:
4.2、redis INCR命令实现- INCR命令,它的全称是increment,用途就是计数器,每执行一次INCR命令,都将key的value自动加1;如果key不存在,那么key的值初始化为0,然后再执行INCR操作。
例如:微信文章id=100,做阅读计算如:
127.0.0.1:6379> incr article:100 (integer) 1 127.0.0.1:6379> incr article:100 (integer) 2 127.0.0.1:6379> incr article:100 (integer) 3 127.0.0.1:6379> incr article:100 (integer) 4 127.0.0.1:6379> get article:100 "4"
- 编写WeChatViewController :
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Api(description = "文章接口")
@RestController
@RequestMapping("/wechat")
@Slf4j
public class WeChatViewController {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@GetMapping(value = "/view")
public void view(Integer id) {
//redis key
String key="article:"+id;
//调用redis的increment计数器命令
long n=this.stringRedisTemplate.opsForValue().increment(key);
log.info("key={},阅读量为{}",key, n);
}
}
- 测试
模拟阅读 编号为2001的文章:
()## 5、SpringCache
5.1、springcache解决的问题- springcache 是spring3.1版本发布出来的,他是对使用缓存进行封装和抽象,通过在方法上使用annotation注解就能拿到缓存结果。
- 正是因为用了annotation,所以它解决了业务代码和缓存代码的耦合度问题,即在不侵入业务代码的基础上,让现有代码即刻支持缓存,让开发人员无感知的使用了缓存。
- 特别注意:
对于redis的缓存,springcache只支持String,其他的Hash 、List、set、ZSet都不支持,这要特别注意。
- 1)引入pom依赖
org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test tk.mybatis mapper-spring-boot-starter 2.0.3 mysql mysql-connector-java io.springfox springfox-swagger2 2.9.2 io.springfox springfox-swagger-ui 2.9.2 org.springframework.boot spring-boot-starter-data-redis org.springframework.boot spring-boot-starter-cache org.apache.commons commons-pool2 2.6.2
- 2)编写redis配置信息
- spring、数据库的配置同 前面模块的配置信息
spring.application.name=spring-boot-mybatis-redis
server.port=9091
# 指定mapper.xml的位置
mybatis.mapper-locations=classpath*:com/xyl/redis/mapper/xml
private RedisSerializer keySerializer() {
return new StringRedisSerializer();
}
private RedisSerializer
5.3、业务逻辑代码编写
- UserService.java
import com.xyl.springcache.entity.User;
import com.xyl.springcache.mapper.UserMapper;
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.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
@CacheConfig(cacheNames = { "user" })
public class UserService {
private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
@Autowired
private UserMapper userMapper;
@Cacheable(key="#id")
public User findUserById(Integer id){
return this.userMapper.selectByPrimaryKey(id);
}
@CachePut(key = "#obj.id")
public User updateUser(User obj){
this.userMapper.updateByPrimaryKeySelective(obj);
return this.userMapper.selectByPrimaryKey(obj.getId());
}
@CacheEvict(key = "#id")
public void deleteUser(Integer id){
User user=new User();
user.setId(id);
user.setDeleted((byte)1);
this.userMapper.updateByPrimaryKeySelective(user);
}
}
- 用到User类、SwaggerConfig、Mapper均与前面模块相同。
- UserController:
import com.xyl.springcache.entity.User;
import com.xyl.springcache.service.UserService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@Api(description = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@ApiOperation("单个用户查询,按userid查用户信息")
@RequestMapping(value = "/findById/{id}", method = RequestMethod.GET)
public UserVO findById(@PathVariable int id) {
User user = this.userService.findUserById(id);
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
return userVO;
}
@ApiOperation("修改某条数据")
@PostMapping(value = "/updateUser")
public void updateUser(@RequestBody UserVO obj) {
User user = new User();
BeanUtils.copyProperties(obj, user);
userService.updateUser(user);
}
@ApiOperation("按id删除用户")
@RequestMapping(value = "/del/{id}", method = RequestMethod.GET)
public void deleteUser(@PathVariable int id) {
this.userService.deleteUser(id);
}
}
5.4、SpringCache常用注解
1)@CacheConfig
@CacheConfig是类级别的注解,统一该类的所有 缓存前缀。
@CacheConfig(cacheNames = { "user" })
public class UserService { }
以上代码,代表了该类的所有缓存都以 “user::” 作为前缀
2)@Cacheable@Cacheable是方法级别的注解,用于将方法的结果缓存起来。
@Cacheable(key="#id")
public User findUserById(Integer id){
return this.userMapper.selectByPrimaryKey(id);
}
-
以上方法被调用时,先从缓存中读取数据,如果缓存没有找到数据,再执行方法体,最后把返回值添加到缓存中。
-
注意:@Cacheable 一般是配合@CacheConfig一起使用。
例如上文的@CacheConfig(cacheNames = { “user” }) 和@Cacheable(key="#id")一起使用时。
调用方法传入id=100,那redis对应的key=user::100 ,value通过采用GenericJackson2JsonRedisSerializer序列化为json。
@CachePut是方法级别的注解,用于更新缓存。
@CachePut(key = "#obj.id")
public User updateUser(User obj){
this.userMapper.updateByPrimaryKeySelective(obj);
return this.userMapper.selectByPrimaryKey(obj.getId());
}
以上方法被调用时,先执行方法体,然后Springcache通过返回值更新缓存,即key = “#obj.id”,value=User
4)@CacheEvict(key = “#id”)@CacheEvict是方法级别的注解,用于删除缓存。
public void deleteUser(Integer id){
User user=new User();
user.setId(id);
user.setDeleted((byte)1);
this.userMapper.updateByPrimaryKeySelective(user);
}
以上方法被调用时,先执行方法体,在通过方法参数删除缓存。
5.5、Springcache需要注意的问题- 1)对于redis的缓存,Springcache只支持String,其他的Hash 、List、set、ZSet都不支持,所以对于Hash 、List、set、ZSet只能用RedisTemplate。
- 2) 对于多表查询的数据缓存,Springcache是不支持的,只支持单表的简单缓存;对于多表的整体缓存,只能用RedisTemplate。



