环境: WIN11 家庭版 | IDEA2023 | JDK17 | MAVEN3.6+ | redis for windows
1."我:请帮我生成一个spring boot 项目"
2.deepseek:
打开 IDEA,点击 New Project
选择 Spring Initializr
Name: 项目名称(如 demo-project)
Location: 项目保存路径
Language: Java
Type: Maven 或 Gradle
Group: 包名(如 com.example)
Artifact: 项目标识(如 demo)
Package name: 自动生成(如 com.example.demo)
JDK: 选择 JDK 17
Java: 17
选择依赖(根据需求选择):
Spring Web - Web 开发
Spring Boot DevTools - 开发工具
Lombok - 简化代码
Spring Data JPA - 数据库操作
MySQL Driver - MySQL 数据库
Thymeleaf - 模板引擎
点击 Create 完成项目创建
**项目结构:**
demo-project/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/example/demo/
│ │ │ ├── DemoApplication.java
│ │ │ ├── controller/
│ │ │ ├── service/
│ │ │ └── repository/
│ │ └── resources/
│ │ ├── application.properties
│ │ ├── static/
│ │ └── templates/
├── pom.xml
└── README.md
package com.example.demo.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot with JDK 17!";
}
@GetMapping("/info")
public String info() {
return "项目运行在 JDK " + System.getProperty("java.version");
}
}
配置application.properties/application.yml
properties
# 服务器配置
server.port=8080
server.servlet.context-path=/
# 应用配置
spring.application.name=demo-project
# 日志配置
logging.level.com.example.demo=DEBUG
运行项目
打开 DemoApplication.java
点击 main 方法旁边的运行按钮
或使用快捷键:Ctrl+Shift+F10
测试应用
在浏览器中访问:
3."在上述项目中添加redis"
4.deepseek:
添加 Redis 依赖
<dependencies>
<!-- Spring Boot Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- JSON 序列化支持 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
配置 redis - application.properties
# redis 基本配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
# 连接池配置
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-wait=-1ms
# 连接超时
spring.redis.timeout=2000ms
新增redis配置类
创建 Redis 配置类来自定义序列化方式
package com.example.demo.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<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// 设置 key 的序列化器
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// 设置 value 的序列化器
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.afterPropertiesSet();
return template;
}
}
创建redis服务类
package com.example.demo.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 设置缓存
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 设置缓存带过期时间
*/
public void set(String key, Object value, long timeout, TimeUnit unit) {
redisTemplate.opsForValue().set(key, value, timeout, unit);
}
/**
* 获取缓存
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 删除缓存
*/
public Boolean delete(String key) {
return redisTemplate.delete(key);
}
/**
* 判断 key 是否存在
*/
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/**
* 设置过期时间
*/
public Boolean expire(String key, long timeout, TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获取过期时间
*/
public Long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
}
创建redis测试方法
package com.example.demo.controller;
import com.example.demo.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisService redisService;
/**
* 设置缓存
*/
@PostMapping("/set")
public String set(@RequestParam String key, @RequestParam String value) {
redisService.set(key, value);
return "设置成功: " + key + " = " + value;
}
/**
* 设置带过期时间的缓存
*/
@PostMapping("/setWithExpire")
public String setWithExpire(@RequestParam String key,
@RequestParam String value,
@RequestParam long timeout) {
redisService.set(key, value, timeout, TimeUnit.SECONDS);
return "设置成功: " + key + " = " + value + ", 过期时间: " + timeout + "秒";
}
/**
* 获取缓存
*/
@GetMapping("/get/{key}")
public Object get(@PathVariable String key) {
Object value = redisService.get(key);
if (value == null) {
return "键不存在: " + key;
}
return value;
}
/**
* 删除缓存
*/
@DeleteMapping("/delete/{key}")
public String delete(@PathVariable String key) {
Boolean result = redisService.delete(key);
return result ? "删除成功: " + key : "删除失败,键不存在: " + key;
}
/**
* 检查键是否存在
*/
@GetMapping("/hasKey/{key}")
public String hasKey(@PathVariable String key) {
Boolean exists = redisService.hasKey(key);
return exists ? "键存在: " + key : "键不存在: " + key;
}
/**
* 获取系统信息
*/
@GetMapping("/info")
public Map<String, Object> getRedisInfo() {
Map<String, Object> info = new HashMap<>();
info.put("javaVersion", System.getProperty("java.version"));
info.put("redisStatus", "已连接");
// 测试 Redis 连接
try {
redisService.set("test_connection", "success", 10, TimeUnit.SECONDS);
info.put("redisTest", "连接正常");
} catch (Exception e) {
info.put("redisTest", "连接失败: " + e.getMessage());
}
return info;
}
}
创建用户对象
package com.example.demo.model;
import java.io.Serializable;
import java.time.LocalDateTime;
public class User implements Serializable {
private Long id;
private String username;
private String email;
private LocalDateTime createTime;
// 构造方法
public User() {}
public User(Long id, String username, String email) {
this.id = id;
this.username = username;
this.email = email;
this.createTime = LocalDateTime.now();
}
// Getter 和 Setter
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
", createTime=" + createTime +
'}';
}
}
redis缓存示例
package com.example.demo.service;
import com.example.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserCacheService {
@Autowired
private RedisService redisService;
private static final String USER_KEY_PREFIX = "user:";
private static final long USER_CACHE_TIMEOUT = 30; // 30分钟
/**
* 缓存用户信息
*/
public void cacheUser(User user) {
String key = USER_KEY_PREFIX + user.getId();
redisService.set(key, user, USER_CACHE_TIMEOUT, TimeUnit.MINUTES);
}
/**
* 从缓存获取用户信息
*/
public User getCachedUser(Long userId) {
String key = USER_KEY_PREFIX + userId;
Object obj = redisService.get(key);
if (obj instanceof User) {
return (User) obj;
}
return null;
}
/**
* 删除用户缓存
*/
public void removeUserFromCache(Long userId) {
String key = USER_KEY_PREFIX + userId;
redisService.delete(key);
}
}
测试用户缓存
package com.example.demo.controller;
import com.example.demo.model.User;
import com.example.demo.service.UserCacheService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserCacheService userCacheService;
/**
* 缓存用户信息
*/
@PostMapping("/cache")
public String cacheUser(@RequestParam Long id,
@RequestParam String username,
@RequestParam String email) {
User user = new User(id, username, email);
userCacheService.cacheUser(user);
return "用户缓存成功: " + user;
}
/**
* 获取缓存用户
*/
@GetMapping("/cache/{id}")
public Object getCachedUser(@PathVariable Long id) {
User user = userCacheService.getCachedUser(id);
if (user == null) {
return "用户未在缓存中找到: " + id;
}
return user;
}
/**
* 删除缓存用户
*/
@DeleteMapping("/cache/{id}")
public String removeCachedUser(@PathVariable Long id) {
userCacheService.removeUserFromCache(id);
return "用户缓存已删除: " + id;
}
}
备注:windows家庭版无法使用docker部署redis,请使用windows专业版、教育版等。
综上,一个简单的springboot + redis框架已经完成,亲测可用,后续将继续扩展集群、分布式,日志、权限等模块
在此记录相关知识点:
spring 两大特点 : IOC/控制反转和依赖注入
ioc个人简单理解:
以前想用对象,我需要 A a = new A;
IOC:先将对象通过配置注解的方式标记,需要使用时,直接使用对象,不需要new,
官方说法是,可以解耦,自我理解为:我先声明我有这个玩意,哪里要用,直接拿来用即可,无需一遍遍的new。
AOP 面向切面,这个理解简单一些,我可以再固定的位置定义切入点,定义后,我可以操作切入点前后执行方法
可以定义before(方法前)、After(方法后)、After-returning(方法执行成功后) After-throwing(方法执行异常后)、Around(环绕)
Around特殊点,可以设置前后执行方法,一般处理事务常用,处理前开启事务,处理后提交事务。
redis 雪崩,击穿,穿透 ,主要是前两者 是key失效,或者redis服务挂了导致请求直接注入数据库,给数据库干蒙蔽。
后者是请求了一个未知key ,要么把null写进redis,要么使用布隆过滤器,原理是预加载,把已知的Key全部缓存进去,如果不存在,直接返回空。
个人理解为,主机启动前先加载缓存数据至redis。
前两者解决方案:雪崩的,设置差异化过期时间,也就是设置失效时间时,给个随机码,每个key不一致,这种情况下,可以避免同时且大量失效。
也可以部署redis集群+哨兵、熔断、服务降级、兜底返回等这个太长了以后写。
击穿的,参照微博热点时间,同一时间大量请求指向一个key,好巧不巧,key失效了,结果就是数据库炸了,现象就是,用户点着没反应,要么报错
这个解决方案查了一下,热点事件一般会轮换设置长期生效,当一个事件不再是热点,则设置短期,当一个事件登上热点,触发设置长期。
另一个就是互斥锁,redis的setnx命令 或 redission(分布式锁)
当发现key失效后,请求key设置setnx成功(lock:key1),并给一个超时时间(5s),然后去查数据库,重新设置缓存,然后删除lock
这个过程中,其他请求发现key失效后,先尝试lock,但因为互斥,所以lock失败,这个时候对方会重新发起请求,或者直接给一个失败结果,主要是保证不会击穿redis,直接将大量请求放到数据库。