搞定了接口文档,前端能顺畅对接了,但系统还藏着几个 “隐形问题”:
每次查图书列表都要查数据库,数据多了会变慢,像奶茶店每次做奶茶都要现煮珍珠,客人等得着急;日志文件一天天变大,占满服务器硬盘,像奶茶店的垃圾桶不清理,越堆越满;服务器重启后,项目要手动双击 jar 包启动,像奶茶店每天要手动开门、开机器,麻烦还容易忘。
今天这篇咱们聚焦 3 个企业级实用优化:Redis 缓存(查得快)、日志归档(不占空间)、服务化部署(自动启),再补个 SQL 索引小技巧,让你的系统从 “能用” 变成 “好用、稳定”,真正符合企业上线标准。
一、先搞懂:优化的核心是 “解决隐形痛点”(奶茶店的优化类比)
新手常觉得 “功能能跑就行,优化没必要”,但企业里 “稳定” 和 “速度” 比 “能用” 更重要,就像奶茶店:
没优化的奶茶店:客人点单后才煮珍珠,等 10 分钟;垃圾桶满了没人倒,卫生差;早上店员迟到,开门晚了;优化后的奶茶店:提前煮好珍珠(缓存),客人点单 1 分钟取餐;每天定时倒垃圾(日志归档),保持整洁;早上自动开门(服务化),不用等店员。
咱们的优化目标也一样,3 个核心痛点对应 3 个解决方案:
| 隐形痛点 | 优化方案 | 企业级价值 | 奶茶店类比 |
|---|---|---|---|
| 频繁查数据库导致接口慢 | Redis 缓存常用数据 | 减少数据库压力,接口响应快 5-10 倍 | 提前煮好珍珠,不用现煮 |
| 日志文件太大占硬盘 | Logback 日志按大小 + 日期归档 | 控制日志体积,避免硬盘满了崩系统 | 每天定时倒垃圾,不堆积水 |
| 服务器重启后要手动启动 | 服务化部署(Windows 服务 / Linux 自启) | 无人值守,重启后自动恢复,减少人工操作 | 早上自动开门,不用等店员 |
二、优化 1:Redis 缓存 —— 给接口装 “预制珍珠”(查得快)
常用数据(比如图书列表、热门图书)每次都查数据库,就像奶茶店每次都现煮珍珠,效率低。用 Redis 把这些数据 “提前存起来”,查的时候先读 Redis,没有再查数据库,接口响应速度能提升好几倍。
步骤 1:装 Redis + 加依赖(准备 “预制珍珠的容器”)
装 Redis:
Windows:下载 Redis 压缩包(Redis 官网),解压后双击启动,黑窗口显示 “Ready to accept connections” 就是成功;Linux:用 Docker 装,一行命令搞定:
redis-server.exe。
docker run -d -p 6379:6379 --name redis-book redis:6.2
加 Redis 依赖:打开,添加 Spring Boot Redis 依赖:
pom.xml
xml
<!-- Spring Boot Redis依赖(操作Redis) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.7.10</version>
</dependency>
<!-- commons-pool2(Redis连接池,提升性能) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
步骤 2:配 Redis 连接(连接 “预制珍珠容器”)
在里加 Redis 配置,告诉项目怎么连 Redis:
application.yml
yaml
spring:
# 其他配置(数据源等)不变...
redis:
host: localhost # Redis地址(服务器部署填服务器IP)
port: 6379 # Redis默认端口
password: "" # Redis密码(默认没密码,生产环境要设)
lettuce: # 连接池配置(用lettuce客户端,比jedis好)
pool:
max-active: 8 # 最大连接数
max-idle: 8 # 最大空闲连接
min-idle: 2 # 最小空闲连接
max-wait: 1000ms # 最大等待时间(超过报错)
步骤 3:写缓存逻辑 ——“预制珍珠 + 更新珍珠”
咱们给 “查询图书列表” 和 “按作者查图书” 加缓存,核心逻辑:查数据时先读 Redis,没有再查数据库,查完存 Redis;改数据时(加 / 删图书)更细 Redis,避免脏数据。
1. 写 Redis 工具类(简化 Redis 操作)
在包下新建
common,封装常用的 “存缓存、取缓存、删缓存” 方法:
RedisUtil.java
java
运行
package com.example.bookmanage.common;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import com.alibaba.fastjson.JSON;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
private final StringRedisTemplate redisTemplate;
// 构造注入(不用@Autowired,更规范)
public RedisUtil(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 存缓存(默认过期时间30分钟)
* @param key 缓存键(比如"book:list")
* @param value 缓存值(对象转JSON字符串)
*/
public void setCache(String key, Object value) {
setCache(key, value, 30, TimeUnit.MINUTES);
}
/**
* 存缓存(自定义过期时间)
*/
public void setCache(String key, Object value, long timeout, TimeUnit unit) {
String valueStr = JSON.toJSONString(value); // 对象转JSON字符串
redisTemplate.opsForValue().set(key, valueStr, timeout, unit);
}
/**
* 取缓存(JSON字符串转对象)
*/
public <T> T getCache(String key, Class<T> clazz) {
String valueStr = redisTemplate.opsForValue().get(key);
if (valueStr == null) {
return null; // 缓存不存在,返回null
}
return JSON.parseObject(valueStr, clazz); // JSON转对象
}
/**
* 删缓存(改数据时调用)
*/
public void deleteCache(String key) {
redisTemplate.delete(key);
}
}
2. 改造 BookService—— 加缓存逻辑
修改的查询和修改方法,整合缓存:
BookService
java
运行
package com.example.bookmanage.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.bookmanage.common.RedisUtil;
import com.example.bookmanage.dao.BookMapper;
import com.example.bookmanage.entity.Book;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Slf4j
@Service
public class BookService extends ServiceImpl<BookMapper, Book> implements IService<Book> {
private final RedisUtil redisUtil;
// 注入RedisUtil
public BookService(RedisUtil redisUtil) {
this.redisUtil = redisUtil;
}
// 1. 查询所有图书(加缓存)
public List<Book> getBookList() {
String cacheKey = "book:list:all"; // 缓存键(唯一标识)
// 先查缓存
List<Book> bookList = redisUtil.getCache(cacheKey, List.class);
if (bookList != null) {
log.info("从Redis缓存获取图书列表");
return bookList;
}
// 缓存不存在,查数据库
bookList = baseMapper.selectList(null);
log.info("从数据库获取图书列表,并存入Redis缓存");
// 存入缓存(30分钟过期)
redisUtil.setCache(cacheKey, bookList);
return bookList;
}
// 2. 按作者查图书(加缓存)
public List<Book> getBooksByAuthor(String author) {
String cacheKey = "book:list:author:" + author; // 缓存键带作者名(唯一)
// 先查缓存
List<Book> bookList = redisUtil.getCache(cacheKey, List.class);
if (bookList != null) {
log.info("从Redis缓存获取作者{}的图书", author);
return bookList;
}
// 查数据库
com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Book> wrapper = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
wrapper.eq("author", author);
bookList = baseMapper.selectList(wrapper);
log.info("从数据库获取作者{}的图书,并存入Redis缓存", author);
// 存入缓存
redisUtil.setCache(cacheKey, bookList);
return bookList;
}
// 3. 添加图书(删缓存,避免脏数据)
@Override
public boolean save(Book book) {
log.info("开始添加图书,书名:{}", book.getBookName());
try {
boolean result = super.save(book);
// 添加成功后,删除图书列表缓存(下次查会重新从数据库读最新数据)
redisUtil.deleteCache("book:list:all");
// 如果知道作者,也删该作者的缓存(比如book:list:author:张三)
if (book.getAuthor() != null) {
redisUtil.deleteCache("book:list:author:" + book.getAuthor());
}
log.info("添加图书成功,已删除相关Redis缓存");
return result;
} catch (Exception e) {
log.error("添加图书失败", e);
throw e;
}
}
// 4. 删除图书(同理,删缓存)
@Override
public boolean removeById(Serializable id) {
// 先查要删除的图书,获取作者(用于删作者缓存)
Book book = getById(id);
boolean result = super.removeById(id);
if (result && book != null) {
// 删缓存
redisUtil.deleteCache("book:list:all");
redisUtil.deleteCache("book:list:author:" + book.getAuthor());
log.info("删除图书成功,已删除相关Redis缓存");
}
return result;
}
}
步骤 4:测试缓存效果 ——“是不是快了”
启动 Redis 和项目,两次访问,看日志:
http://localhost:8080/book/list
第一次访问:日志显示 “从数据库获取图书列表,并存入 Redis 缓存”;第二次访问:日志显示 “从 Redis 缓存获取图书列表”,接口响应时间从 “几十毫秒” 降到 “几毫秒”,效果立竿见影!
🔍 图示 1:Redis 缓存效果对比示意图
| 访问次数 | 日志内容 | 响应时间 | 核心逻辑 |
|---|---|---|---|
| 第一次 | 从数据库获取图书列表,并存入 Redis 缓存 | 50ms | 缓存空→查库→存缓存 |
| 第二次 | 从 Redis 缓存获取图书列表 | 3ms | 缓存有→直接读缓存,不查库 |
三、优化 2:日志归档 —— 给日志 “定时倒垃圾”(不占空间)
之前的日志存在,但只会按天分割,不会删旧日志,时间长了会堆几百个 G 的日志文件。咱们用 Logback 配置 “按大小 + 日期双分割”,并设置 “保留 30 天,超过自动删除”,像奶茶店 “每天倒垃圾,超过 30 天的旧垃圾直接清”。
D:/java-project-logs/book-manage/
步骤 1:修改 logback-spring.xml—— 配置日志归档
更新,添加 “大小分割” 和 “过期删除” 规则:
src/main/resources/logback-spring.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 日志格式和路径(不变) -->
<property name="LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n" />
<property name="LOG_PATH" value="D:/java-project-logs/book-manage" />
<!-- 1. 控制台输出(不变) -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 2. 文件输出(按日期+大小分割,过期删除) -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 当前日志文件路径 -->
<file>${LOG_PATH}/book-manage.log</file>
<!-- 分割规则:按日期(天)+ 大小(500MB)分割 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- 分割后的日志文件名:book-manage-2025-10-20.0.log(0表示当天的第1个500MB文件) -->
<fileNamePattern>${LOG_PATH}/book-manage-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>500MB</maxFileSize> <!-- 单个文件最大500MB,超过分割 -->
<maxHistory>30</maxHistory> <!-- 保留30天的日志,超过自动删除 -->
<totalSizeCap>10GB</totalSizeCap> <!-- 日志总大小不超过10GB,避免占满硬盘 -->
</rollingPolicy>
<encoder>
<pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 3. 全局日志级别(不变) -->
<root level="INFO">
<appender-ref ref="CONSOLE" />
<appender-ref ref="FILE" />
</root>
</configuration>
步骤 2:看日志归档效果 ——“垃圾清干净了吗”
启动项目后,日志文件会按以下规则生成:
当天日志先存在;当文件达到 500MB,自动分割成
book-manage.log、
book-manage-2025-10-20.1.log;超过 30 天的日志(比如 10 月 1 日的)会自动删除,总大小不会超过 10GB。
book-manage-2025-10-20.2.log
🔍 图示 2:日志归档后的文件结构示意图
plaintext
D:/java-project-logs/book-manage/
├─ book-manage.log # 当前日志文件(当天)
├─ book-manage-2025-10-20.1.log # 10月20日第1个500MB文件
├─ book-manage-2025-10-20.2.log # 10月20日第2个500MB文件
├─ book-manage-2025-10-19.1.log # 10月19日的日志(保留)
└─ (10月1日及之前的日志已自动删除)
四、优化 3:服务化部署 —— 让项目 “自动开门”(不用手动启)
之前服务器重启后,要手动双击 jar 包或输命令启动项目,麻烦还容易漏。咱们把项目做成 “系统服务”:Windows 用 WinSW 做成服务,Linux 用 systemd 设置自启,像奶茶店 “早上自动开门、开机器”,不用人工干预。
场景 1:Windows 服务器 —— 用 WinSW 做成系统服务
步骤 1:下载 WinSW 工具
进 WinSW 官网(WinSW GitHub),下载(64 位系统),重命名为
WinSW-x64.exe,放到
book-service.exe(和 jar 包同目录)。
D:/java-projects/
步骤 2:写服务配置文件
在同目录新建(文件名要和 exe 一致),配置服务信息:
book-service.xml
xml
<service>
<!-- 服务名称(在Windows服务里显示) -->
<id>BookManageService</id>
<!-- 服务显示名称(易读) -->
<name>图书管理系统服务</name>
<!-- 服务描述 -->
<description>Java图书管理系统的系统服务,重启后自动启动</description>
<!-- 要执行的命令(java -jar 你的jar包) -->
<executable>java</executable>
<arguments>-jar "D:/java-projects/book-manage-1.0-SNAPSHOT.jar"</arguments>
<!-- 日志配置(服务启动/停止日志) -->
<logpath>D:/java-projects/service-logs/</logpath>
<logmode>rotate</logmode> <!-- 日志按大小分割 -->
<!-- 启动模式(自动启动) -->
<startmode>Automatic</startmode>
<!-- 停止命令(优雅停止) -->
<stopexecutable>taskkill</stopexecutable>
<stoparguments>/f /t /im java.exe /fi "windowtitle eq BookManageService"</stoparguments>
</service>
步骤 3:安装并启动服务
以 “管理员身份” 打开 CMD,进入,执行命令:
D:/java-projects/
bash
# 安装服务
book-service.exe install
# 启动服务
book-service.exe start
然后打开 “Windows 服务”(Win+R 输入),能看到 “图书管理系统服务”,状态是 “正在运行”,启动类型是 “自动”—— 重启服务器后,服务会自动启动,不用再手动点 jar 包!
services.msc
🔍 图示 3:Windows 服务安装效果示意图
| 操作命令 | 服务状态 | 核心效果 |
|---|---|---|
|
服务列表新增 “图书管理系统服务” | 服务安装成功 |
|
服务状态变为 “正在运行” | 服务启动,项目开始运行 |
| 重启服务器 | 服务自动恢复 “正在运行” 状态 | 无需手动干预,自动启动 |
场景 2:Linux 服务器 —— 用 systemd 设置自启
步骤 1:写 systemd 服务文件
新建文件,内容如下:
/etc/systemd/system/book-manage.service
ini
[Unit]
# 服务描述
Description=Book Manage System Service
# 依赖:网络和Redis启动后再启动该服务
After=network.target redis.service
[Service]
# 启动用户(用普通用户,避免root权限风险)
User=root
# 工作目录(jar包所在目录)
WorkingDirectory=/home/java-projects/
# 启动命令
ExecStart=/usr/bin/java -jar /home/java-projects/book-manage-1.0-SNAPSHOT.jar
# 停止命令(优雅停止)
ExecStop=/bin/kill -15 $MAINPID
# 重启策略:异常退出后自动重启
Restart=on-failure
# 重启等待时间:2秒
RestartSec=2s
[Install]
# 开机自启:关联到multi-user.target(多用户模式)
WantedBy=multi-user.target
步骤 2:启用并启动服务
执行命令:
bash
# 重新加载systemd配置(让系统识别新服务)
systemctl daemon-reload
# 启动服务
systemctl start book-manage.service
# 设置开机自启
systemctl enable book-manage.service
# 查看服务状态(确认是否在运行)
systemctl status book-manage.service
看到 “active (running)” 就说明服务启动成功,重启 Linux 服务器后,执行,服务会自动恢复运行。
systemctl status book-manage.service
🔍 图示 4:Linux 服务状态示意图
bash
[root@localhost ~]# systemctl status book-manage.service
● book-manage.service - Book Manage System Service
Loaded: loaded (/etc/systemd/system/book-manage.service; enabled; vendor preset: disabled)
Active: active (running) since Tue 2025-10-21 10:00:00 CST; 5min ago
Main PID: 1234 (java)
Tasks: 20 (limit: 2345)
Memory: 256.0M
CGroup: /system.slice/book-manage.service
└─1234 /usr/bin/java -jar /home/java-projects/book-manage-1.0-SNAPSHOT.jar
五、小优化:SQL 索引 —— 给数据库 “装快速查找器”
查询 “按作者查图书” 时,如果 book 表数据多(比如 10 万条),会扫全表,慢。给字段加索引,像给数据库装 “快速查找器”,能秒定位到该作者的所有图书。
author
步骤 1:给 book 表加索引
在 Navicat 执行 SQL:
sql
-- 给book表的author字段加普通索引(适合频繁查询的字段)
CREATE INDEX idx_book_author ON book(author);
步骤 2:测试索引效果
加索引前,执行,看 “type” 列是 “ALL”(全表扫描);加索引后,再执行同样的 SQL,“type” 列变成 “ref”(走索引查询),查询时间从 “几百毫秒” 降到 “几毫秒”。
EXPLAIN SELECT * FROM book WHERE author = '张三';
六、优化时的 5 个小心注意
Redis 缓存脏数据:添加 / 删除图书后忘了删缓存,导致查的是旧数据 —— 记住 “改数据必删缓存”,或用 “缓存过期时间” 兜底;日志路径不存在:Logback 配置的(比如
LOG_PATH)没手动创建,会报 “找不到路径” 错误 —— 先创建目录再启动项目;Windows 服务权限不足:WinSW 没以管理员身份运行,安装服务时会报 “拒绝访问”—— 右键 CMD→“以管理员身份运行”;Linux 服务 jar 包路径错:
D:/java-project-logs/book-manage里的 java 路径或 jar 包路径写错,服务启动失败 —— 用
ExecStart查 java 实际路径,用
which java确认 jar 包是否存在;索引加太多:给 book 表的所有字段都加索引,导致添加 / 删除图书变慢 —— 只给 “频繁查询的字段”(如 author)加索引,索引是 “查询快、写慢”,平衡取舍。
ls

