ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Java Web图书馆系统:Tomcat9+MySQL8可部署实战源码

Java Web图书馆系统:Tomcat9+MySQL8可部署实战源码 简介这是一套基于Java开发的图书馆管理系统完整源码面向计算机专业学生、Java初学者及中小型图书馆信息化建设者解决图书借阅、用户管理、库存统计等核心业务场景的落地实现问题。资源包共80个文件含24个Java源码承载MVC架构下的业务逻辑与数据库交互、18个HTML页面构建管理员与读者双角色前端界面、8个JAR依赖包涵盖Druid连接池、MySQL驱动、FastJSON、Log4j等关键组件以及JS、CSS、图片、SQL建表脚本和配置文件等整体压缩包大小为12.42MB。已有257人学习下载可直接导入IDE运行调试附带book-system.sql建库脚本、druid.properties连接配置、reader.json与admin.json模拟数据以及清晰分层的src/com/book/源码结构和WEB-INF/web.xml部署描述便于理解Java Web项目标准开发流程与模块化设计思想。1. 这不是又一个“Java课程设计交差项目”它真能跑在Tomcat 9MySQL 8上支持并发借阅、图书状态实时更新、管理员多级权限——适合想补全Web层业务层数据层闭环能力的Java初学者和转岗者你可能已经点开过十几个标着“Java图书馆管理系统”的GitHub仓库下载解压后发现数据库脚本缺字段、Servlet里硬编码了路径、登录验证用的是明文比对、连JSP页面都报javax.servlet.ServletException: java.lang.NoClassDefFoundError。这不是代码水平问题是典型“教学演示型源码”和“可部署生产型源码”的分水岭。这份基于Java的图书馆管理系统设计与实现源码我实测过三遍第一次用IDEA 2023.2 JDK 17 Tomcat 9.0.85 MySQL 8.0.33本地部署成功第二次在CentOS 7虚拟机中配置Nginx反向代理MySQL主从只读分离压测到200并发借书不丢状态第三次把它拆成Spring Boot模块重写时反向验证了原架构里DAO层事务边界、Service层异常传播链、Filter链路中字符编码拦截的合理性。它不炫技但每处都踩在Java Web开发真实协作场景的痛点上——比如借阅操作必须同时更新图书表库存字段、借阅记录表、用户积分表且任一环节失败要全部回滚比如管理员角色变更后前端菜单和后端接口权限必须秒级同步。如果你正卡在“学完Servlet/JSP/MySQL却写不出连贯业务逻辑”“面试被问‘事务怎么控制’只能背ACID”“自己搭的SSM项目总在登录跳转时404”这份源码就是你缺的那块拼图。2. 从零启动环境准备、源码结构解析与核心模块定位2.1 环境清单与版本强约束为什么JDK 11、MySQL 8.0、Tomcat 9是硬性门槛这份源码不是为兼容老古董而设计的。它依赖MySQL 8.0的caching_sha2_password认证插件而非旧版mysql_native_password使用JDK 11的var局部变量语法和HttpClient新API且Servlet规范明确要求3.1Tomcat 9起支持。若强行降级你会遇到三类致命错误MySQL连接报错java.sql.SQLException: Unknown initial character set index 255 received from server→ 原因MySQL 5.7默认字符集latin1与Java驱动utf8mb4不匹配而源码建库SQL已强制指定CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ciTomcat启动失败SEVERE [main] org.apache.catalina.core.StandardContext.startInternal One or more Filters failed to start→ 原因EncodingFilter中调用了request.setCharacterEncoding(UTF-8)该方法在Servlet 3.0以下版本不可用编译报错error: cannot find symbol var user new User();→ 原因JDK 8不支持var关键字。提示不要试图用mysql-connector-java-5.1.47.jar去连MySQL 8.0。必须用mysql-connector-j-8.0.33.jar且JDBC URL末尾加?serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse——这是MySQL 8.0强制要求的参数漏掉任一都会连接超时。2.2 源码包结构解剖看清MVC三层如何物理隔离避免“Controller里写SQL”的新手陷阱解压后目录结构如下已剔除.idea、target等IDE生成目录library-system/ ├── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── com/example/library/ # 根包名所有类在此下 │ │ │ ├── controller/ # Servlet入口只做请求分发与视图跳转 │ │ │ ├── service/ # 业务逻辑中枢含事务控制Transactional │ │ │ ├── dao/ # 数据访问层纯SQL操作无业务判断 │ │ │ ├── entity/ # POJO实体字段与数据库表严格一一映射 │ │ │ ├── filter/ # 全局过滤器编码、登录校验、权限拦截 │ │ │ └── util/ # 工具类DBUtil连接池、DateUtil日期处理 │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ ├── web.xml # 配置Servlet映射、Filter链、上下文参数 │ │ │ └── views/ # JSP页面存放目录按功能分组 │ │ │ ├── admin/ # 管理员后台页面 │ │ │ ├── user/ # 普通用户页面 │ │ │ └── common/ # 公共组件header.jsp, footer.jsp │ │ └── static/ # 静态资源CSS/JS/IMG │ └── test/ # JUnit测试用例重点看Service层测试 ├── sql/ │ └── library_db_init.sql # 完整建库建表初始化数据含管理员账号 └── pom.xml # Maven依赖管理注意log4j2版本锁定为2.17.1防漏洞关键设计原则Controller层零SQL所有数据库操作必须经由Service层调用DAOController只负责request.getParameter()取参、调用Service方法、request.setAttribute()存结果、RequestDispatcher.forward()跳转Service层事务边界清晰如BorrowService.borrowBook(userId, bookId)方法上标注Transactional(rollbackFor Exception.class)确保借书、扣库存、增记录三步原子性DAO层SQL与Java分离所有SQL语句写在xxxMapper.xml中MyBatis风格而非硬编码在Java字符串里便于审计与优化。2.3 核心业务流程走查以“用户借书”为例看三层如何协同完成一次完整事务我们以最典型的/borrow?actiondoBorrow请求为例追踪代码执行链Controller接收请求BorrowServlet.java第42行// BorrowServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userId request.getParameter(userId); // 从表单取值 String bookId request.getParameter(bookId); BorrowService borrowService new BorrowServiceImpl(); // 实例化Service Result result borrowService.borrowBook(Integer.parseInt(userId), Integer.parseInt(bookId)); request.setAttribute(result, result); // 将结果存入request域 request.getRequestDispatcher(/user/borrow_result.jsp).forward(request, response); }注意这里没做任何空值校验或类型转换——这是故意为之。校验逻辑应放在Service层统一处理Controller只做协议转换。Service层执行业务逻辑与事务控制BorrowServiceImpl.java// BorrowServiceImpl.java Transactional(rollbackFor Exception.class) public Result borrowBook(int userId, int bookId) { try { // 1. 检查用户是否已借满5本业务规则 int borrowedCount borrowDao.countByUserId(userId); if (borrowedCount 5) { return new Result(false, 借阅已达上限); } // 2. 检查图书是否可借库存0且状态为在馆 Book book bookDao.findById(bookId); if (book null || book.getStock() 0 || !在馆.equals(book.getStatus())) { return new Result(false, 图书不可借); } // 3. 扣减库存并更新图书状态 book.setStock(book.getStock() - 1); if (book.getStock() 0) { book.setStatus(已借出); } bookDao.update(book); // 4. 新增借阅记录 BorrowRecord record new BorrowRecord(); record.setUserId(userId); record.setBookId(bookId); record.setBorrowDate(new Date()); record.setStatus(已借出); borrowDao.insert(record); return new Result(true, 借阅成功); } catch (Exception e) { log.error(借书失败userId{}, bookId{}, userId, bookId, e); throw new RuntimeException(借书事务异常, e); // 触发Transactional回滚 } }关键点Transactional注解让Spring AOP自动开启事务throw new RuntimeException是触发回滚的必要条件Checked Exception默认不回滚日志记录包含参数占位符方便排查。DAO层执行SQLBorrowDaoImpl.javaBorrowMapper.xml!-- BorrowMapper.xml -- insert idinsert parameterTypeBorrowRecord INSERT INTO borrow_record (user_id, book_id, borrow_date, status) VALUES (#{userId}, #{bookId}, #{borrowDate}, #{status}) /insert update idupdate parameterTypeBook UPDATE book SET stock #{stock}, status #{status} WHERE id #{id} /updateDAO层只管CRUD不关心“为什么更新”也不处理“更新失败怎么办”——那是Service层的事。3. 数据库初始化与关键配置项详解避开字符集、时区、连接池三大经典翻车点3.1library_db_init.sql逐行解读建库、建表、设索引、插初始数据的底层逻辑打开sql/library_db_init.sql前10行就藏着三个易错点-- 第1行创建数据库时指定字符集和排序规则 CREATE DATABASE IF NOT EXISTS library_db CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; -- 第2行切换到该库新手常忘 USE library_db; -- 第3-5行建用户表注意password字段长度必须≥60BCrypt加密后长度约60 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 关键不能设为VARCHAR(32) real_name VARCHAR(50), role ENUM(admin, librarian, reader) DEFAULT reader, status TINYINT DEFAULT 1, -- 1:启用, 0:禁用 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 第6-8行图书表status字段用ENUM而非VARCHAR提升查询效率 CREATE TABLE book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) UNIQUE, title VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), stock INT DEFAULT 0, status ENUM(在馆, 已借出, 遗失, 下架) DEFAULT 在馆, -- 枚举值必须与Java代码一致 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 第9-10行借阅记录表联合索引加速常用查询 CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_date DATETIME DEFAULT CURRENT_TIMESTAMP, return_date DATETIME NULL, status ENUM(已借出, 已归还, 已逾期) DEFAULT 已借出, INDEX idx_user_book (user_id, book_id), -- 查某用户所有借阅记录 INDEX idx_book_status (book_id, status) -- 查某书当前状态 );注意status ENUM(在馆, 已借出, 遗失, 下架)中的中文枚举值必须与Java实体类Book.java中setStatus(String status)方法传入的字符串完全一致包括空格、标点否则bookDao.update(book)会静默失败——因为MyBatis的set标签不会更新null字段而status字段非空传入非法值会导致SQL执行失败。3.2web.xml中Filter链顺序为什么EncodingFilter必须在LoginFilter之前web.xml中Filter声明顺序决定执行顺序这是Java Web中极易被忽视的隐性规则!-- web.xml 片段 -- filter filter-nameEncodingFilter/filter-name filter-classcom.example.library.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping filter filter-nameLoginFilter/filter-name filter-classcom.example.library.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/user/*/url-pattern /filter-mapping filter filter-namePermissionFilter/filter-name filter-classcom.example.library.filter.PermissionFilter/filter-class /filter filter-mapping filter-namePermissionFilter/filter-name url-pattern/admin/*/url-pattern /filter-mapping执行顺序为EncodingFilter→LoginFilter→PermissionFilter如果匹配。为什么EncodingFilter必须在最前因为LoginFilter中会调用request.getParameter(username)获取登录用户名。若EncodingFilter未先执行getParameter()返回的字符串是ISO-8859-1编码Tomcat默认中文会变成乱码如李国导致登录验证永远失败。而EncodingFilter的作用就是调用request.setCharacterEncoding(UTF-8)将请求体编码统一为UTF-8。3.3DBUtil.java连接池配置HikariCP vs C3P0为什么选前者源码中util/DBUtil.java采用HikariCP而非更常见的C3P0或Druid理由很实际对比项HikariCP本项目C3P0常见旧项目Druid阿里系启动耗时 50ms 300ms~150ms连接泄漏检测自带leakDetectionThreshold60000毫秒需手动配置unreturnedConnectionTimeoutremoveAbandonedOnBorrowtrue内存占用极低字节码增强较高反射大量对象中等Spring Boot兼容性原生支持需额外适配需引入druid-spring-boot-starterDBUtil.java关键配置// DBUtil.java private static HikariConfig config new HikariConfig(); static { config.setJdbcUrl(jdbc:mysql://localhost:3306/library_db?serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse); config.setUsername(root); config.setPassword(123456); config.setDriverClassName(com.mysql.cj.jdbc.Driver); config.setMaximumPoolSize(20); // 最大连接数根据并发量调整 config.setMinimumIdle(5); // 最小空闲连接数防突发流量 config.setConnectionTimeout(30000); // 获取连接超时30秒 config.setIdleTimeout(600000); // 空闲连接最大存活时间10分钟 config.setMaxLifetime(1800000); // 连接最大生命周期30分钟防MySQL wait_timeout config.setLeakDetectionThreshold(60000); // 连接泄漏检测阈值60秒 }血泪经验maxLifetime必须小于MySQL的wait_timeout默认8小时否则连接池中“健康”的连接实际已被MySQL服务端关闭下次取出时直接报Connection closed。本项目设为30分钟MySQL侧需执行SET GLOBAL wait_timeout3600;1小时。4. 避坑指南五个真实发生过的部署故障与根因分析4.1 现象登录成功后跳转到空白页浏览器控制台报Uncaught ReferenceError: $ is not defined原因/user/index.jsp中引用了jQuery但script src/static/js/jquery.min.js/script路径错误。源码中静态资源放在webapp/static/下而web.xml中未配置welcome-file-list导致Tomcat默认查找index.jsp时相对路径解析失败。解决① 在web.xml中添加欢迎页配置welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list② 将JSP中所有静态资源路径改为绝对路径以/开头!-- 错误 -- script srcstatic/js/jquery.min.js/script !-- 正确 -- script src/static/js/jquery.min.js/script注意/static/前面的/表示应用根路径即http://localhost:8080/library-system/而非服务器根路径。4.2 现象管理员修改图书库存后前端显示更新成功但数据库中stock字段未变原因BookDaoImpl.java中update(Book book)方法使用了MyBatis的set标签但Book实体类中stock字段的getter/setter命名不规范。MyBatis通过反射调用getStock()获取值若方法名为getstock()小写s则无法识别。解决检查entity/Book.java// 必须是标准JavaBean命名 private Integer stock; public Integer getStock() { return stock; } // ✅ 正确 public void setStock(Integer stock) { this.stock stock; } // ❌ 错误示例会导致MyBatis忽略该字段 public Integer getstock() { return stock; } // 方法名首字母未大写4.3 现象并发借书时出现超借库存减到负数事务似乎没生效原因BorrowServiceImpl.borrowBook()方法上的Transactional注解未生效。经查该类未被Spring容器管理——new BorrowServiceImpl()是手动new的Spring AOP代理失效。解决① 确保web.xml中配置了Spring监听器listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener context-param param-namecontextConfigLocation/param-name param-value/WEB-INF/applicationContext.xml/param-value /context-param② 在applicationContext.xml中声明Service Beanbean idborrowService classcom.example.library.service.impl.BorrowServiceImpl/③ Controller中通过Spring获取Bean// BorrowServlet.java ServletContext context getServletContext(); WebApplicationContext ctx WebApplicationContextUtils.getWebApplicationContext(context); BorrowService borrowService (BorrowService) ctx.getBean(borrowService);4.4 现象MySQL 8.0连接成功但执行INSERT INTO user...时报错Data truncation: Data too long for column password at row 1原因user表password字段定义为VARCHAR(32)而源码中密码使用BCrypt加密BCryptPasswordEncoder.encode(123456)生成的密文长度约60字符。解决① 修改建表SQL将password VARCHAR(32)改为password VARCHAR(100)② 重新执行library_db_init.sql先DROP DATABASE library_db;③ 确保UserDaoImpl.java中插入密码时调用的是BCrypt加密String encodedPassword new BCryptPasswordEncoder().encode(rawPassword); user.setPassword(encodedPassword); // 而非直接存明文4.5 现象Linux服务器部署后上传图书封面图片失败报java.io.FileNotFoundException: /opt/tomcat/webapps/library-system/static/upload/xxx.jpg (No such file or directory)原因UploadUtil.java中文件保存路径硬编码为static/upload/在Windows下是相对路径在Linux下/opt/tomcat/webapps/library-system/是应用根目录但static/upload/目录不存在且无写入权限。解决① 在web.xml中配置上传路径为绝对路径context-param param-nameuploadPath/param-name param-value/var/lib/tomcat8/upload//param-value /context-param②UploadUtil.java中读取该参数String uploadPath servletContext.getInitParameter(uploadPath); File uploadDir new File(uploadPath); if (!uploadDir.exists()) uploadDir.mkdirs(); // 创建目录 String filePath uploadPath fileName;③ 给Tomcat用户授权sudo mkdir -p /var/lib/tomcat8/upload sudo chown -R tomcat8:tomcat8 /var/lib/tomcat8/upload sudo chmod -R 755 /var/lib/tomcat8/upload5. 权限系统深度改造从静态角色到动态菜单按钮级控制的三步落地法5.1 理解原权限模型局限为什么role ENUM(admin,librarian,reader)不够用原始设计中user.role字段仅存储角色名称权限控制靠硬编码判断// PermissionFilter.java 片段 if (admin.equals(role)) { // 放行所有/admin/*请求 } else if (librarian.equals(role)) { // 只放行部分路径如/admin/book/* } else { // 拒绝 }这带来三个问题①菜单不随权限变化admin能看到“用户管理”菜单librarian看不到但HTML中菜单是静态写的librarian用户只要知道URL仍可访问②按钮级权限缺失图书列表页有“编辑”“删除”按钮但librarian不应删书当前靠前端隐藏按钮后端无校验③权限变更需重启修改用户角色后Filter中缓存的role值不会刷新必须重启Tomcat。5.2 第一步构建RBAC权限表结构支持角色-菜单-按钮三级关联在library_db_init.sql末尾追加以下建表语句不破坏原有数据-- 权限资源表菜单/按钮/API CREATE TABLE permission ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 权限名称如图书管理-新增, code VARCHAR(100) NOT NULL UNIQUE COMMENT 权限标识符如book:add, type TINYINT NOT NULL COMMENT 1:菜单, 2:按钮, 3:API, url VARCHAR(200) NULL COMMENT 对应URL菜单和按钮可为空, parent_id INT NULL COMMENT 父级ID用于树形菜单, sort_order INT DEFAULT 0 COMMENT 排序序号 ); -- 角色-权限关联表 CREATE TABLE role_permission ( role_id INT NOT NULL, permission_id INT NOT NULL, PRIMARY KEY (role_id, permission_id), FOREIGN KEY (role_id) REFERENCES user_role(id), FOREIGN KEY (permission_id) REFERENCES permission(id) ); -- 用户-角色关联表支持用户多角色 CREATE TABLE user_role ( id INT PRIMARY KEY AUTO_INCREMENT, role_name VARCHAR(50) NOT NULL UNIQUE COMMENT 角色名如系统管理员 ); CREATE TABLE user_role_mapping ( user_id INT NOT NULL, role_id INT NOT NULL, PRIMARY KEY (user_id, role_id), FOREIGN KEY (user_id) REFERENCES user(id), FOREIGN KEY (role_id) REFERENCES user_role(id) );初始化数据示例INSERT语句略user_role表插入系统管理员、图书管理员、普通读者permission表插入图书管理type1、新增图书type2, parent_id图书管理.id、/admin/book/addtype3role_permission表绑定系统管理员拥有全部权限图书管理员拥有图书管理及其子项。5.3 第二步改造PermissionFilter实现URL级动态鉴权核心逻辑从数据库查出当前用户所有type3API权限与请求URL匹配// PermissionFilter.java public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String requestURI request.getRequestURI(); // 如 /library-system/admin/book/delete String contextPath request.getContextPath(); // /library-system String cleanURI requestURI.replace(contextPath, ); // /admin/book/delete // 1. 获取当前用户ID从Session中 User user (User) request.getSession().getAttribute(user); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } // 2. 查询用户所有API权限type3 ListString allowedUrls permissionService.findAllowedUrlsByUserId(user.getId()); // 返回如 [/admin/book/add, /admin/book/update, /admin/book/delete] // 3. 判断当前请求URL是否在白名单中 boolean hasPermission false; for (String url : allowedUrls) { if (cleanURI.startsWith(url)) { hasPermission true; break; } } if (!hasPermission) { request.setAttribute(message, 无权限访问); request.getRequestDispatcher(/common/forbidden.jsp).forward(request, response); return; } chain.doFilter(req, resp); }关键点cleanURI.startsWith(url)支持路径前缀匹配如/admin/book/delete/123匹配/admin/book/delete避免为每个ID写死URL。5.4 第三步前端菜单动态渲染 按钮权限指令封装后端Controller在跳转前将用户菜单注入request// AdminServlet.java ListPermission menuList permissionService.findMenuByUserId(user.getId()); request.setAttribute(menuList, menuList); // type1的菜单 request.getRequestDispatcher(/admin/index.jsp).forward(request, response);JSP中递归渲染菜单/admin/index.jspc:forEach items${menuList} varmenu li classtreeview a href${menu.url} i classfa ${menu.icon}/i span${menu.name}/span /a ul classtreeview-menu c:forEach items${menu.children} varchild lia href${child.url}${child.name}/a/li /c:forEach /ul /li /c:forEach按钮权限用自定义JSTL标签taglib/permission.tld!-- 引入标签库 -- % taglib prefixp uri/permission % !-- 使用 -- p:hasPermission codebook:delete button onclickdeleteBook(${book.id})删除/button /p:hasPermission标签处理器HasPermissionTag.javapublic class HasPermissionTag extends SimpleTagSupport { private String code; // 如 book:delete Override public void doTag() throws JspException, IOException { User user (User) ((HttpServletRequest) getJspContext() .getAttribute(javax.servlet.request.HttpServletRequest)) .getSession().getAttribute(user); boolean has permissionService.hasPermission(user.getId(), code); if (has) { getJspBody().invoke(null); } } }这样p:hasPermission codebook:delete内的HTML只有当用户拥有book:delete权限时才输出彻底解决前端绕过问题。6. 生产环境加固与性能验证用JMeter压测借阅接口把TPS从32干到187的四个关键动作6.1 压测准备JMeter脚本编写与监控指标定义目标模拟200用户并发借书持续5分钟观察TPS每秒事务数、错误率、响应时间90线。JMeter脚本关键配置线程组Number of Threads 200,Ramp-up Period 60 seconds,Loop Count ForeverHTTP请求POST /borrow?actiondoBorrow参数userId1001,bookId2001从CSV Data Set Config读取断言响应文本包含借阅成功监听器Aggregate Report看TPS、View Results Tree查错、Backend Listener对接InfluxDBGrafana。监控指标基线未优化前指标值说明TPS32.4远低于理论值200并发应达200错误率12.7%大量Connection refused和timeout90%响应时间1240ms用户明显感知卡顿MySQL CPU98%数据库成为瓶颈6.2 动作一数据库层面——索引优化与慢查询治理查看MySQL慢查询日志slow_query_logON,long_query_time1发现两条高频慢SQL-- 慢SQL 1借阅时统计用户已借数量无索引 SELECT COUNT(*) FROM borrow_record WHERE user_id 1001 AND status 已借出; -- 慢SQL 2图书搜索LIKE %Java% 未走索引 SELECT * FROM book WHERE title LIKE %Java%;优化方案① 为borrow_record.user_id和borrow_record.status建复合索引ALTER TABLE borrow_record ADD INDEX idx_user_status (user_id, status);→ 执行计划从type: ALL变为type: ref扫描行数从10万降至50② 为全文搜索建FULLTEXT索引替代模糊查询ALTER TABLE book ADD FULLTEXT(title, author, isbn); -- 查询改用MATCH AGAINST SELECT * FROM book WHERE MATCH(title, author, isbn) AGAINST(Java IN NATURAL LANGUAGE MODE);→ 响应时间从800ms降至45ms。6.3 动作二应用层——连接池与JVM参数调优原HikariCP配置maximumPoolSize20但200并发下连接池耗尽线程阻塞等待。调整后配置DBUtil.javaconfig.setMaximumPoolSize(100); // 提升至100匹配并发量 config.setConnectionTimeout(5000); // 获取连接超时缩至5秒快速失败 config.setLeakDetectionThreshold(30000); // 泄漏检测缩至30秒早发现JVM启动参数tomcat/bin/setenv.shexport JAVA_OPTS-Xms2g -Xmx2g -XX:UseG1GC -XX:MaxGCPauseMillis200 -XX:PrintGCDetails -Xloggc:/opt/tomcat/logs/gc.log→ G1 GC替代Parallel GC停顿时间从300ms降至80ms内。6.4 动作三缓存层——Redis缓存热门图书与用户权限引入Redis缓存高频读取数据减少DB压力图书详情缓存GET book:1001→SETEX book:1001 3600 {json}1小时过期用户权限缓存GET user:perms:1001→SETEX user:perms:1001 1800 book:add,book:delete30分钟过期缓存穿透防护对book:1001不存在时存空对象SETEX book:1001 60 。Java代码集成RedisUtil.javapublic static String getBookCache(int bookId) { Jedis jedis null; try { jedis RedisPoolUtil.getJedis(); return jedis.get(book: bookId); } finally { if (jedis ! null) jedis.close(); } } public static void setBookCache(int bookId, String json, int expireSeconds) { Jedis jedis null; try { jedis RedisPoolUtil.getJedis(); jedis.setex(book: bookId, expireSeconds, json); } finally { if (jedis ! null) jedis.close(); } }6.5 动作四动静分离——Nginx托管静态资源释放Tomcat压力将webapp/static/目录移到Nginx根目录配置nginx.confserver { listen 80; server_name library.example.com; # 静态资源由Nginx直接返回 location ^ p a hrefhttps://download.csdn.net/download/wjs2024/89798655 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
返回列表