3.1 建表
CREATE TABLE `t_user` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',
`password` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '密码',
`sex` TINYINT(4) NOT NULL DEFAULT '0' COMMENT '性别 0=女 1=男 ',
`deleted` TINYINT(4) UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除标志,默认0不删除,1删除',
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8 COMMENT='用户表';
3.2 创建SpringBoot项目
3.2.1 pom.xml
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.10.RELEASE</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>redis-01-crud</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<!--SpringBoot通用依赖模块-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>2.0.8</version>
</dependency>
<!--SpringBoot与Redis整合依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--springCache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--springCache连接池依赖包-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<!--Mysql数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!--hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--persistence-->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.2.2 application.yaml
server:
port: 8001
spring:
application:
name: guli_user
datasource:
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false
redis:
host: 192.168.123.133
port: 6379
database: 0
lettuce:
pool:
max-active: 8 #连接池最大连接数(使用负值表示没有限制) 默认 8
max-wait: -1ms #连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1,记得加入单位ms,不然idea报红色
max-idle: 8 #连接池中的最大空闲连接 默认 8
min-idle: 0 #连接池中的最小空闲连接 默认 0
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印SQL日志
global-config:
db-config:
logic-delete-field: 1 # 逻辑删除标志
logic-not-delete-value: 0
3.2.3 MyBatisPlus生成代码
略
3.2.4 配置类
RedisConfig
@Configuration
@Slf4j
public class RedisConfig {
@Bean
public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(lettuceConnectionFactory);
//设置key序列化方式string
redisTemplate.setKeySerializer(new StringRedisSerializer());
//设置value的序列化方式为json
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
Knife4jConfig
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfig {
@Bean
public Docket adminApiConfig(){
List<Parameter> pars = new ArrayList<>();
ParameterBuilder tokenPar = new ParameterBuilder();
tokenPar.name("token")
.description("用户token")
.defaultValue("")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(false)
.build();
pars.add(tokenPar.build());
//添加head参数end
Docket adminApi = new Docket(DocumentationType.SWAGGER_2)
.groupName("adminApi")
.apiInfo(adminApiInfo())
.select()
//只显示admin路径下的页面
.apis(RequestHandlerSelectors.basePackage("com.fystart.redis")) //注意包名
.paths(PathSelectors.regex("/admin/.*")) //注意去掉
.build()
.globalOperationParameters(pars);
return adminApi;
}
private ApiInfo adminApiInfo(){
return new ApiInfoBuilder()
.title("后台管理系统-API文档")
.description("本文档描述了后台管理系统微服务接口定义")
.version("1.0")
.contact(new Contact("zhangsan", "http://baidu.com", "zhangsan@qq.com"))
.build();
}
}
3.2.5 crud业务代码
UserController
@Api(value = "用户User接口")
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@ApiOperation("数据库新增5条记录")
@RequestMapping(value = "/add", method = RequestMethod.POST)
public void addUser() {
for (int i = 0; i < 5; i++) {
User user = new User();
user.setUsername("zzyy" + i);
user.setPassword(IdUtil.simpleUUID().substring(0, 6));
//[0,2)
user.setSex(new Random().nextInt(2));
userService.addUser(user);
}
}
@ApiOperation("删除一条记录")
@RequestMapping(value = "/delete/{id}", method = RequestMethod.POST)
public void deleteUser(@PathVariable Integer id) {
userService.deleteUser(id);
}
@ApiOperation("修改一条记录")
@RequestMapping(value = "/update", method = RequestMethod.POST)
public void updateUser(@RequestBody User user) {
userService.updateUser(user);
}
@ApiOperation("查询一条记录")
@RequestMapping(value = "/find/{id}", method = RequestMethod.GET)
public User findUserById(@PathVariable Integer id) {
return userService.findUserById(id);
}
}
UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
public static final String CACHE_KEY = "user:";
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserMapper userMapper;
@Override
public void addUser(User user) {
//第一步:先成功插入mysql
int flag = userMapper.insert(user);
if (flag > 0) {
//第二步:需要再次查询一下mysql,把数据捞回来
User queryUser = this.getById(user.getId());
//第三步:将捞出来的user存进redis,完成新增功能的数据一致性
redisTemplate.opsForValue().set(CACHE_KEY + queryUser .getId(), queryUser );
} else {
throw new RuntimeException("插入失败~");
}
}
@Override
public void deleteUser(Integer id) {
//删除mysql中的数据
boolean flag = this.removeById(id);
if (flag) {
//把redis中的数据删除
redisTemplate.delete(CACHE_KEY + id);
}
}
@Override
public void updateUser(User user) {
int flag = userMapper.updateById(user);
if (flag > 0) {
//将数据查出来放入redis
user = userMapper.selectById(user.getId());
redisTemplate.opsForValue().set(CACHE_KEY + user.getId(), user);
}
}
/**
* 业务逻辑并没有写错,对于中小厂(QPS <= 1000)可以使用,但是大厂不行,还有点问题
*
* @param id
* @return
*/
@Override
public User findUserById(Integer id) {
User queryUser = null;
//1 现从redis里面查询,如果有直接返回结果,没有再去查询mysql
queryUser = (User) redisTemplate.opsForValue().get(CACHE_KEY + id);
if (queryUser == null) {
//redis里面无,继续查询mysql
queryUser = userMapper.selectById(id);
if (queryUser == null) {
//说明redis和mysql都没有数据
return null;
} else {
//mysql中有,需要将数据写回redis,保证下一次的缓存命中率
redisTemplate.opsForValue().set(CACHE_KEY + id, queryUser);
}
}
return queryUser;
}
/**
* 由于实际工作中,一般redis的key都是会设置过期时间的。如果突然碰到过期时间到期了,某个热点key突然失效,
* 导致redis中的热点key突然失效导致缓存击穿的问题。
*
* 加强:避免突然key失效了,打爆mysql,做一下预防,尽量不出现击穿的情况
*
* @param id
* @return
*/
public User getById2(Integer id) {
User queryUser = null;
//1 现从redis里面查询,如果有直接返回结果,没有再去查询mysql
queryUser = (User) redisTemplate.opsForValue().get(CACHE_KEY + id);
if (queryUser == null) {
//2 对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql(避免缓存击穿)
synchronized (UserService.class) {
queryUser = (User) redisTemplate.opsForValue().get(CACHE_KEY + id);
//3 第二次查redis还是null,可以去查mysql了
if (queryUser == null) {
queryUser = userMapper.selectById(id);
//mysql中也没有,直接返回null
if (queryUser == null) {
return null;
} else {
//5 mysql里面有数据,需要回写redis,完成数据一致性的同步工作
redisTemplate.opsForValue().setIfAbsent(CACHE_KEY + id, queryUser, 7, TimeUnit.DAYS);
}
}
}
}
return queryUser;
}
}