一、为什么要缓存
1、原因
用缓存,主要有两个用途:高性能、高并发。
- 高性能
非实时变化的数据-查询mysql耗时需要300ms,存到缓存redis,每次查询仅仅1ms,性能瞬间提升百倍。 - 高并发
mysql 单机支撑到2K QPS就容易报警了,如果系统中高峰时期1s请求1万,仅单机mysql是支撑不了的,但是使用缓存的话,单机支撑的并发量轻松1s几万~十几万。
原因是缓存位于内存,内存对高并发的良好支持。
2、问题
常见的缓存问题:
1、缓存与数据库双写不一致
2、缓存雪崩、缓存穿透
3、缓存并发竞争
二、Spring boot的缓存机制
1、SpringCache概述
- SpringCache本身是一个缓存体系的抽象实现,并没有具体的缓存能力,要使用SpringCache还需要配合具体的缓存实现来完成。
- 虽然如此,但是SpringCache是所有Spring支持的缓存结构的基础,而且所有的缓存的使用最后都要归结于SpringCache,那么一来,要想使用SpringCache,还是要仔细研究一下的。
2、缓存注解
SpringCache缓存功能的实现是依靠下面的这几个注解完成的。
@EnableCaching:开启缓存功能
@Cacheable:定义缓存,用于触发缓存
@CachePut:定义更新缓存,触发缓存更新
@CacheEvict:定义清除缓存,触发缓存清除
@Caching:组合定义多种缓存功能
@CacheConfig:定义公共设置,位于class之上
三、spring boot整合mysql+redis缓存项目
1、准备数据源
mysql表:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) NULL DEFAULT NULL COMMENT '用户编号',
`MONEY` double NULL DEFAULT NULL COMMENT '金额',
PRIMARY KEY (`ID`) USING BTREE,
INDEX `FK_Reference_8`(`UID`) USING BTREE,
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES (1, 46, 1000);
INSERT INTO `account` VALUES (2, 45, 1000);
INSERT INTO `account` VALUES (3, 46, 2000);
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
`ID` int(11) NOT NULL COMMENT '编号',
`ROLE_NAME` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称',
`ROLE_DESC` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述',
PRIMARY KEY (`ID`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '院长', '管理整个学院');
INSERT INTO `role` VALUES (2, '总裁', '管理整个公司');
INSERT INTO `role` VALUES (3, '校长', '管理整个学校');
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称',
`birthday` datetime(0) NULL DEFAULT NULL COMMENT '生日',
`sex` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别',
`address` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 73 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (41, '老王', '2018-02-27 17:47:08', '男', '北京');
INSERT INTO `user` VALUES (42, '小二王', '2018-03-02 15:09:37', '女', '北京金燕龙');
INSERT INTO `user` VALUES (43, '小二王', '2018-03-04 11:34:34', '女', '北京金燕龙');
INSERT INTO `user` VALUES (45, '传智播客', '2018-03-04 12:04:06', '男', '北京金燕龙');
INSERT INTO `user` VALUES (46, '老王', '2018-03-07 17:37:26', '男', '北京');
INSERT INTO `user` VALUES (48, '小马宝莉', '2018-03-08 11:44:00', '女', '北京修正');
INSERT INTO `user` VALUES (53, '司理理', '2020-04-14 00:22:25', '女', '北齐');
-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
`UID` int(11) NOT NULL COMMENT '用户编号',
`RID` int(11) NOT NULL COMMENT '角色编号',
PRIMARY KEY (`UID`, `RID`) USING BTREE,
INDEX `FK_Reference_10`(`RID`) USING BTREE,
CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT,
CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (41, 1);
INSERT INTO `user_role` VALUES (45, 1);
INSERT INTO `user_role` VALUES (41, 2);
SET FOREIGN_KEY_CHECKS = 1;
properties.xml配置
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://地址/2020offer?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
#缓存配置
spring.cache.cache-names=cacheUser
spring.redis.database=0
spring.redis.host=
spring.redis.port=6379
#spring.redis.password=
spring.redis.timeout=20000ms
pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jp</groupId>
<artifactId>redis-cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-cache</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- mybatis连接配置-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 缓存-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、数据库查询并缓存
用户类
package com.jp.rediscache.bean;
import java.io.Serializable;
import java.time.Period;
/**
* @program: redis-cache
* @description: 用户类
* @author: CoderPengJiang
* @create: 2020-04-27 17:11
**/
public class User implements Serializable {
private Integer id;
private String username;
private String sex;
private String address;
private String birthday;
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getBirthday() {
return birthday;
}
public void setBirthday(String birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
", birthday='" + birthday + '\'' +
'}';
}
}
用户持久层UserMapper
package com.jp.rediscache.mapper;
import com.jp.rediscache.bean.User;
public interface UserMapper {
User getUserById(Integer id);
}
用户持久层配置UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jp.rediscache.mapper.UserMapper">
<select id="getUserById" resultType="com.jp.rediscache.bean.User">
select * from user where user.id=#{id}
</select>
</mapper>
用户业务层并且开启缓存配置
package com.jp.rediscache.service;
import com.jp.rediscache.bean.User;
import com.jp.rediscache.mapper.UserMapper;
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;
import javax.annotation.Resource;
/**
* @program: redis-cache
* @description: 用户业务层
* @author: CoderPengJiang
* @create: 2020-04-27 17:27
**/
@Service
@CacheConfig(cacheNames = "cacheUser")
public class UserService {
@Autowired
UserMapper userMapper;
@Cacheable
public User getUserById(Integer id){
System.out.println("从数据库中查询数据-------");
return userMapper.getUserById(id);
}
//删除缓存中的数据
@CacheEvict
public void deleteUserById(Integer id) {
System.out.println("deleteUserById>>>" + id);
}
@Cacheable
public User getUserId(Integer id){
System.out.println("从数据库中查询数据-------");
User user=new User();
user.setId(id);
return user;
}
//更新缓存中的数据
@CachePut(key = "#user.id")
public User updateUserById(User user) {
return user;
}
}
开启缓存
package com.jp.rediscache;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@MapperScan(basePackages = "com.jp.rediscache.mapper")
//开启缓存
@EnableCaching
public class RedisCacheApplication {
public static void main(String[] args) {
SpringApplication.run(RedisCacheApplication.class, args);
}
}
开启测试
package com.jp.rediscache;
import com.jp.rediscache.bean.User;
import com.jp.rediscache.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RedisCacheApplicationTests {
@Autowired
UserService userService;
@Test
void contextLoads() {
}
@Test
public void getUserById(){
User user = userService.getUserById(46);
userService.deleteUserById(46);
User user1 = userService.getUserById(46);
System.out.println(user);
}
@Test
public void getUser(){
User user1 = userService.getUserId(1);
System.out.println(user1);
}
@Test
public void updateUser(){
User user1=userService.getUserById(46);
User user2=new User();
user2.setId(46);
user2.setAddress("湖南");
user2.setBirthday("2020-04-27");
user2.setSex("男");
user2.setUsername("**");
userService.updateUserById(user2);
User user3=userService.getUserById(46);
System.out.println(user1);
System.out.println(user3);
}
}
四、结论
springboot整合redis的缓存核心思路就是当开发者调用一个方法时,将方法的参数和返回值作为key/value缓存起来,当再次调用该方法时,如果缓存中有数据,就直接从中获取,否则再去执行该方法。