ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

图书管理系统JavaWeb项目从零实现:MySQL建表、Servlet三层架构与Tomcat部署

图书管理系统JavaWeb项目从零实现:MySQL建表、Servlet三层架构与Tomcat部署 简介基于javaweb的图书管理系统设计与实现源码面向高校毕业设计、期末大作业及课程设计场景适合具备Java基础的学生下载参考。项目采用JavaWeb经典分层结构包含图书信息管理、用户信息管理、Excel批量导入导出等核心模块代码注释详细个人手打高分项目系统功能完善、界面简洁易用简单部署即可运行。资源包共352个文件压缩后约64.79MB类型涵盖91个jar运行依赖、37个Java源码、91个class编译文件、41个Excel表格、14个HTML页面、13个CSS样式、12个JS脚本、5个SQL数据库脚本及DOCX文档说明等能清晰对应后端逻辑、前端页面与数据库初始化。已有544人浏览学习适合作为高分毕业设计或期末项目直接参考。配套文档说明与代码注释有助于读者梳理业务流程、理解关键实现并快速完成本地环境部署与二次开发。1. 图书管理系统这个JavaWeb经典项目值不值得从零手写每年Java课程设计榜单上图书管理系统都是出现频率最高的选题几乎成了JavaWeb课程设计的“标准答案”。你要做的不是再去网上找一份看不懂的整包代码而是搞清楚这个项目从需求分析、数据库设计到代码落地的完整链路。这篇博文把图书管理系统拆成一张可执行的地图覆盖MySQL表结构设计、Servlet三层架构、连接池参数配置、部署到Tomcat全流程最后还会讲清楚高分答辩要往哪里使劲。为什么这个项目值得认真做因为它麻雀虽小五脏俱全覆盖了JavaWeb里最常见的CRUD、分页、模糊查询、登录注册、事务处理这些知识点拿出去面试也能直接讲。适合两类人一类是刚学完Servlet和JSP、想用完整项目串知识点的在校生一类是多年没碰JavaWeb、需要快速捡起这套技术栈做内部管理系统的开发者。2. 系统功能拆解与MySQL数据表设计先把表结构立住2.1 功能模块划分管理员端和读者端到底该拆多细图书管理系统的功能边界很容易被网上那些大而全的案例带跑偏动辄十几个模块光菜单就能写两页。我一般建议按角色把功能切成两条线一条管书一条管人。管理员端核心功能是图书分类管理、图书信息维护增删改查、读者管理、借书还书登记、借阅记录查询、超期罚款处理。读者端核心功能是登录、浏览图书、按书名或作者检索、查看个人借阅历史。有一条容易被忽略但答辩时会被问到要不要做预约借书功能它看着简单实际要处理“图书当前是否可借”的状态流转涉及事务和并发如果你的核心功能已经写熟可以当作加分项如果时间紧先砍掉把借阅流程的异常处理写好更划算。还有一个现实问题是是否需要内置管理员账号必须内置而且要在建表阶段就把结构预留好管理员和读者可以共用一张user表用一个role字段区分也可以单独拆admin表。共用一张表更省事角色权限控制靠Servlet过滤器就能搞定适合课程设计。2.2 核心表结构从ER图到建表SQL的完整落地以共用一张用户表的方案为例数据库最少需要五张表用户表包含管理员和读者、图书分类表、图书表、借阅记录表、罚款记录表。用户表的取舍点在于信息冗余度。姓名、学号/工号、联系方式、注册时间、状态是必须的。密码存储不做加密的话答辩会被问住建议不要用明文可以用MD5加盐哪怕只是简单加固定盐值也比裸存强得多。图书表里最关键的是两个字段总库存量和当前可借量。这两个字段的初始值是相等的每借出一本可借量减一每归还一本可借量加一。这个逻辑听起来简单真正出问题的地方在于如果有人同时借两本同一本书代码里计算可借量要减几如果只在应用层做加减数据库没有做约束并发高一点就会出现负数库存。课程设计不会上高并发但表设计时要养成好习惯可借量字段用INT unsigned同时借出操作放在事务里执行。借阅记录表设计时要注意一个细节借书时间和应还时间是两个字段应还时间可以用借书时间加固定天数比如30天自动计算不要在录入数据时手动填。实际还书时间是允许为空的空代表尚未归还这个字段是判断该书是否“在借”的关键。下面给出一版建表SQL字段数量和数据类型都是课程设计够用的最小集CREATE DATABASE library_system DEFAULT CHARACTER SET utf8mb4; USE library_system; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50) DEFAULT NULL, student_no VARCHAR(30) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT 0-管理员, 1-读者, status TINYINT NOT NULL DEFAULT 1 COMMENT 1-正常, 0-禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL UNIQUE, description VARCHAR(255) ); CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, isbn VARCHAR(20) NOT NULL, book_name VARCHAR(100) NOT NULL, author VARCHAR(50), category_id INT, publisher VARCHAR(100), publish_date DATE, total_count INT NOT NULL DEFAULT 1, available_count INT NOT NULL DEFAULT 1, FOREIGN KEY (category_id) REFERENCES t_category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, due_time DATETIME NOT NULL, return_time DATETIME DEFAULT NULL, status TINYINT DEFAULT 1 COMMENT 1-借出, 2-已还, 3-超期未还, FOREIGN KEY (user_id) REFERENCES t_user(id), FOREIGN KEY (book_id) REFERENCES t_book(id) ); CREATE TABLE t_fine_record ( id INT PRIMARY KEY AUTO_INCREMENT, record_id INT NOT NULL, user_id INT NOT NULL, fine_amount DECIMAL(6,2) DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, is_paid TINYINT DEFAULT 0, FOREIGN KEY (record_id) REFERENCES t_borrow_record(id), FOREIGN KEY (user_id) REFERENCES t_user(id) );建表SQL里值得单独说明的参数有两处。第一处是AUTO_INCREMENT和PRIMARY KEY的配合只要InnoDB引擎主键自增就是默认的聚簇索引结构按主键查询和范围扫描性能最好课程设计阶段不要尝试用ISBN或学号做主键业务字段做唯一约束可以但主键保持无业务含义更安全。第二处是DEFAULT CHARSETutf8mb4如果你不写这一句MySQL 5.7默认引擎是InnoDB、默认字符集是latin1存中文会出现乱码或写入失败很多部署问题不是代码引起的是建表时字符集没指定造成的。2.3 初始化数据内置管理员和样例图书数据的技巧建完表之后必须往里插初始化数据否则项目一启动就报“表不存在”或者页面空荡荡影响调试效率。管理员账号我一般固定写死为admin/123456并且初始化SQL里直接提供明文密码方便前后端联调。真正上线前再改成加密值课程设计阶段不必过度设计。样例图书数据的技巧是不要只插三五十本至少要三百本以上这样测试分页和搜索时才能看到效果。数据可以写一段Java代码循环插入也可以用一条SQL通过笛卡尔积快速生成。推荐后者省时间且不涉及代码调试。INSERT INTO t_user (username, password, real_name, role) VALUES (admin, 202cb962ac59075b964b07152d234b70, 系统管理员, 0); -- 密码明文是123202cb962... 是它的MD5值说明文档里要写清楚这个对应关系插入图书测试数据的技巧是借助MySQL的内存临时表和变量循环DELIMITER $$ CREATE PROCEDURE init_books() BEGIN DECLARE i INT DEFAULT 1; WHILE i 300 DO INSERT INTO t_book(isbn, book_name, author, category_id, publisher, total_count, available_count) VALUES (CONCAT(978-7-, LPAD(i, 5, 0)), CONCAT(测试图书-, i), 张三, 1, 人民邮电出版社, 5, 5); SET i i 1; END WHILE; END$$ DELIMITER ; CALL init_books();存储过程的性能不算好但一次性生成测试数据足够用了。需要注意项目代码里不要出现这种存储过程生产环境临时表和大循环都是要避免的这里只是为了让你在课程设计阶段快速填满数据验证分页组件是否正常。3. 三层架构与核心代码实现Servlet、Service、DAO的边界划在哪3.1 包结构规划未雨绸缪比临时整理省力十倍很多同学的JavaWeb课程设计乱成一团所有Servlet堆在一个包底下导致改一个功能要翻半天文件。从一开始就把包结构分清楚后面写代码顺很多。常见分层方式按职责拆成五个包com.xxx.library.servlet放控制层、com.xxx.library.service放业务接口和实现类、com.xxx.library.dao放持久层接口和实现类、com.xxx.library.entity放实体类、com.xxx.library.util放工具类数据库连接、MD5加密等。个人经验是多加一个com.xxx.library.filter包专门放过滤器和监听器。登录拦截过滤器是每个JavaWeb项目都需要的单独用一个包放filter相关的类找起来不用回忆“登录校验的代码写哪儿了”。实体类的设计直接对应五张表属性和字段一一对应不需要额外写复杂逻辑。但是有一种常见写法需要避开直接在实体类里塞业务方法比如在Book实体里写一个isAvailable()方法判断是否可借。这看起来方便实际上模糊了领域模型和数据模型的边界课程设计里更稳的做法是让Service层判断实体类只做数据的载体。3.2 数据库连接池为什么你该用Druid而不是DriverManager图书管理系统里每个请求都要操作数据库如果按JDBC最原始的方式用DriverManager.getConnection()每次请求都会创建一次物理连接实验到一半就会发现响应越来越慢甚至报连接超时。这就是连接池存在的意义。配置Druid连接池的步骤很简单先引入依赖再写一个properties配置文件最后在工具类里用静态代码块初始化。以Maven工程为例pom.xml需要加入druid和mysql-connector-java两个依赖。dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency然后是druid.properties配置文件jdbc.driverClassNamecom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/library_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password你的数据库密码 initialSize5 maxActive20 maxWait10000JDBC URL里的三个参数有讲究characterEncodingutf8解决中文传参乱码serverTimezoneAsia/Shanghai解决MySQL 8.x版本时区报错useSSLfalse解决连接时警告。这三个参数不写全运行时会出各种幺蛾子。然后写工具类核心是这个package com.xxx.library.util; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax.sql.DataSource; import java.io.InputStream; import java.sql.Connection; import java.util.Properties; public class DbUtil { private static DataSource dataSource; static { try { Properties props new Properties(); InputStream in DbUtil.class.getClassLoader() .getResourceAsStream(druid.properties); props.load(in); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(数据库连接池初始化失败: e.getMessage()); } } public static Connection getConnection() throws Exception { return dataSource.getConnection(); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception ignored) { } } } } }静态代码块里最关键的逻辑是初始化失败时抛出异常而不是吞掉。很多新手把catch块里写e.printStackTrace()打印完以后项目能启动但之后每次拿连接都是空指针排查起来非常头疼。直接抛出初始化错误让程序启动时就暴露数据库配置问题这是工程里最省时间的做法。3.3 借书的完整事务链路从Controller到DAO的代码怎么写借书这个操作在所有图书管理系统中是逻辑最重的功能它要同时改动两张表往t_borrow_record插入一条记录把t_book表的available_count减一。两步操作必须在同一事务里否则会出现借阅记录有了、库存没减的脏数据。Servlet层负责接收参数、转换类型、调用Service不写业务逻辑。Service层是事务的边界Connection要么提交全部、要么回滚全部。DAO层只负责SQL执行。放在事务里执行时Connection必须是通过同一个连接拿到的代码如下public class BorrowServiceImpl implements BorrowService { Override public String borrowBook(int userId, int bookId) { Connection conn null; try { conn DbUtil.getConnection(); // 关闭自动提交手动控制事务边界 conn.setAutoCommit(false); BookDao bookDao new BookDaoImpl(); BorrowRecordDao borrowDao new BorrowRecordDaoImpl(); // 第一步检查图书可借数量 Book book bookDao.findById(conn, bookId); if (book null) { return 图书不存在; } if (book.getAvailableCount() 0) { return 该书暂无库存; } // 第二步插入借阅记录使用SQL中的now()生成借书时间 borrowDao.insert(conn, new BorrowRecord(userId, bookId, null, null, null, 1)); // 第三步库存减一 bookDao.decreaseAvailableCount(conn, bookId); // 全部成功提交事务 conn.commit(); return 借书成功; } catch (Exception e) { // 任何异常都回滚避免库存和借阅记录不一致 if (conn ! null) { try { conn.rollback(); } catch (Exception ex) { ex.printStackTrace(); } } e.printStackTrace(); return 借书失败 e.getMessage(); } finally { DbUtil.close(conn); } } }这里有一个关键点DAO层的方法签名里必须带Connection参数。有的教程把获取连接的逻辑放在DAO内部Service层只管调用一旦涉及事务两个DAO用的是各自拿到的连接事务就失去了意义。所以凡是Service层需要保证原子性的操作必须让Service拿到唯一的Connection对象通过参数传递给DAO方法。借书时还有一个细节插入借阅记录时不要把借书时间放在Java代码里生成再传进去。直接用SQL函数NOW()生成本地时间避免应用服务器和数据库服务器时区不一致导致的时间偏移。4. IDEA运行JavaWeb项目的完整配置从导入源码到Tomcat启动4.1 从零配置项目的步骤JDK版本、Maven仓库与Artifact课程设计项目的源码下载下来以后用IDEA打开第一关就是环境匹配。热门搜索词“idea运行javaweb项目配置”背后全是踩坑的人这里给出最稳妥的配置路径。第一步确认本机JDK版本与项目要求的版本一致。图书管理系统用JDK 8完全够用选JDK 8的另一个原因是JavaWeb课程设计里很多代码是早期语法风格换到JDK 17以后Tomcat和Maven插件的兼容性会多出很多不必要的问题。IDEA里File → Project Structure → SDK直接选择8。第二步Maven配置的关键是阿里云镜像。打开settings.xml在mirrors节点加入mirror idaliyunmaven/id mirrorOfcentral/mirrorOf urlhttps://maven.aliyun.com/repository/public/url /mirror不加这个镜像加载依赖时能从中央仓库下载的只有最新版且网络通畅的情况能成功一旦网络波动就卡住半天不动。新手很难判断是网络问题还是IDEA配置错了换镜像仓库是解决依赖下载问题的第一步。第三步配置Tomcat运行环境。这个步骤涉及如下路径选择IDEA菜单 Run → Edit Configurations → 左上角 → 选择 Tomcat Server → LocalConfiguration标签页选择本机Tomcat安装目录不是bin目录Server Settings里的HTTP port用8080不改默认值。Deployment标签页点击号选择Artifact把项目的war包部署上去Application context设置成/library最后启动时访问http://localhost:8080/library/才是项目的入口。这里设置成/library或/都行但是要注意如果路径写的是全路径/library/浏览器访问时漏掉末尾斜杠还是能自动补全的。4.2 mvn常用命令与跳过测试的执行技巧在IDEA里纯手工点击图形界面启动Tomcat没问题但写代码过程中频繁改配置要一次次重启效率很低。熟练以后可以直接使用Maven命令避开IDE工具栏的抽风。命令行启动项目的调试流程一般是这样# 清理之前的编译产物 mvn clean # 编译且跳过测试节省MySQL依赖检查的时间 mvn install -DskipTests -Dmaven.test.skiptrue # 启动Tomcat插件项目里如果配了tomcat7-maven-plugin则可用 mvn tomcat7:run用-DskipTests跳过测试代码编译能节省不少时间因为课程设计里很少有人正经写JUnit测试测试代码反而可能引用一些没配置的测试资源导致编译失败。如果只是改了JSP页面连Maven重新编译都不用在IDEA里直接热部署即可菜单Build → Recompile。4.3 war包部署与外部Tomcat脱离IDE的兜底方案使用IDEA内置Tomcat运行没问题但答辩前建议掌握war包手动部署的兜底方案这能应对计算机环境预设不合理的神秘问题。先在IDEA右侧Maven面板点package打包出war文件然后把这个war包复制到Tomcat的webapps目录下注意Tomcat版本要与Java版本匹配Tomcat 9配JDK 8是经典组合。最后启动Tomcat用catalina.sh或catalina.bat看操作系统决定观察日志输出看到Deploying web application archive和Server startup in xxx ms两行日志说明启动正常。5. 图书管理系统十大常见问题排查部署、数据库与代码级避坑5.1 项目启动时数据库连接失败现象启动Tomcat时控制台报Cannot create PoolableConnectionFactory或Access denied for user rootlocalhost。原因90%是druid.properties里的账号密码与本地MySQL不一致或者MySQL服务本身没有启动。解决先检查MySQL服务状态。Windows用服务管理器看mysql服务是否运行。Linux用systemctl status mysqld或service mysql status。服务正常后用命令行mysql -u root -p手动证明账号密码可以登录再回来看配置文件。注意properties文件里密码不要带前后空格空格也会导致认证失败。5.2 前端页面中文乱码的完整排查链路现象页面显示一串问号或者菱形乱码。原因链条上的任何一环编码不一致都会乱码常见的有三种数据库表是latin1字符集、JSP页面没有指定UTF-8、浏览器请求时没有加字符集参数。解决如果抓包或打印SQL发现数据库里存的就已经是乱码说明建表字符集有问题用ALTER TABLE t_book CONVERT TO CHARACTER SET utf8mb4;批量修复。如果数据库存的是对的页面显示乱码检查JSP顶部是否有contentTypetext/html;charsetUTF-8。请求参数乱码的场景在web.xml加一个CharacterEncodingFilter对POST请求做强制UTF-8转码filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping上面的过滤器来自Spring框架如果项目没有用Spring那就自己写一个Filter原理一样request.setCharacterEncoding(UTF-8)然后chain.doFilter传给下一层。5.3 删除分类时提示外键约束限制现象删除某个图书分类时SQL报错Cannot delete or update a parent row: a foreign key constraint fails。原因t_book表里还有图书引用着这个分类数据库保护了引用完整性不允许删父表数据。解决代码里做联查删除分类之前先去t_book表统计该categoryId下的图书数量如果大于零要么把图书全部改分到其他分类要么禁止删除要么删除分类时关联删除图书。设计上和代码实现上我推荐第二种做法即返回提示“该分类下还有图书无法删除”简单而且不会误删数据。提示外键约束不是越高配越好。课程设计项目里使用外键表达引用关系代码里手动处理一致性属于主流做法把外键全部去掉、单靠业务代码维护的写法在互联网公司更常见但课程设计展示外键会显得数据库知识扎实建议保留。5.4 已经有管理员账号页面登录状态经常丢失现象登录后跳转首页点击几次页面又弹回登录页。原因Session超时时间默认只有30分钟或者登录校验的过滤器拦截了所有请求包括登录页面本身和静态资源。解决两个办法配合使用。第一web.xml配置延长Session超时session-config session-timeout120/session-timeout /session-config第二过滤器里加白名单判断如果请求路径是login.jsp、dologin、/css、/js、/images开头的直接放行。这里的具体实现是拿request.getRequestURI()contains判断前缀先判断是否放行再判断session是否为空顺序不能反过来。5.5 借书成功但库存没减现象界面上提示借书成功打开图书详情发现可借数量还是原来的数字。原因Service层各DAO使用了不同Connection事务根本没有生效插入借阅记录后库存更新失败也没能回滚。解决把所有DAO方法改造为收Connection参数的方式并参照前文的BorrowServiceImpl把setAutoCommit(false)、commit、rollback写到Service层。一个判断标准Service层的入口方法里看到几次getConnection就应该有几次隐患。5.6 输入书名搜索不出结果现象数据库里有《计算机网络》这本书在搜索框里输入“计算机”或“网络”都查不到东西。原因SQL里用的是等号判断而不是模糊匹配。解决SQL改成WHERE book_name LIKE CONCAT(%, ?, %)用PreparedStatement占位符而不是直接把参数拼进SQL字符串后者会导致SQL注入风险。还有一个冷门注意点搜索关键词包含特殊字符百分号、下划线时要用ESCAPE关键字把它们转义否则搜索与%会被当成通配符。5.7 分页页码从1开始还是从0开始现象点击第2页拿到的数据是从第21条开始的而不是从第11条或者出现重复数据。原因SQL中LIMIT的两个参数含义是LIMIT offset, rows很多人把pageNum直接传到第一个参数上导致页码为2时跳过了前2条。解决先把页码从1归零再乘以每页大小int offset (pageNum - 1) * pageSize。封装分页工具类的时候把计算逻辑写在PageBean里面前端只需要传pageNum和pageSize。5.8 报表导出Excel时导出为空现象点击“导出台账”按钮下载下来的Excel是空的而页面表格上有数据。原因JSP页面的表单提交和导出请求混在一起了提交了导出请求但Controller接收的查询条件参数与页面表达不一致。解决导出功能传参时把当前页面的查询条件一并带上保证SELECT语句和页面查询语句的条件完全一致。另一个坑表格列表页有分页导出的SQL也套了LIMIT导出结果只有当前页的数据正确做法是导出功能单独执行一次无LIMIT的查询。5.9 修改图书信息后列表不刷新现象编辑完一本图书返回列表页看到的还是旧信息。原因浏览器缓存或者Servlet转发导致页面未重新请求。解决修改操作完成后不要转发到JSP用response.sendRedirect(bookList)走一次重新请求。这是PRG模式Post-Redirect-Get最基本的应用防止刷新重复提交也能让列表拿到最新数据。5.10 项目打包后运行报找不到JDBC驱动现象IDEA里运行正常打war包后放到外部Tomcat启动一访问数据库就报ClassNotFoundException: com.mysql.cj.jdbc.Driver。原因IDEA里依赖是IDEA帮你加载的但war包的WEB-INF/lib目录下缺少mysql驱动jar包。解决确认依赖作用范围Maven里scopeprovided/scope的依赖不会打进去。检查pom.xmlmysql-connector-java需要去掉provided或者直接改成默认的compile。重新package后解压war包确认WEB-INF/lib下有驱动文件。6. 答辩与二次开发加分项让图书管理系统从及格到优秀的小技巧图书管理系统的功能做到能用只是及格线高分的关键在于答辩时能讲清楚几个“黑匣子”事务边界怎么控制的、密码怎么存储的、权限拦截怎么做的、分页查询怎么避免全表扫描的。给分页SQL加一个组合索引是性价比极高的加分点。图书表的检索条件一般是book_name和category_id那么索引就建在(category_id, book_name)上注意顺序要把等值查询的字段放前面。这个原理答出来评委立刻知道你对索引有理解而不是只会写CRUD。另一个容易被忽视的高分技巧是登录功能加上验证码。不用集成第三方验证码组件自己用Java2D画一个简单的四位随机验证码把文本塞进Session提交时对比。这能展示你理解Session状态和图片绘制两个知识点代码量不大值得做。图书借阅到期提醒可以作为二次开发的进阶功能。用定时任务每天扫描t_borrow_record表把due_time小于当前时间且status为1的记录筛选出来生成待提醒列表。JavaWeb最轻量的定时方案是ServletContextListener里启动一个ScheduledExecutorService这是纯JDK的方案不需要额外引入Quartz但足以展示你对定时任务的认知。项目文档是容易被低估的得分点。数据字典里除了表结构说明还应补充自增主键的使用理由、InnoDB和MyISAM引擎选型的对比、为什么额外记录available_count而不是每次都COUNT统计在借数量。最后这个问题的标准答案是COUNT统计需要多表联查在数据量大时性能不可控冗余一个整数字段用事务保证最终一致性是典型的空间换时间。从我的角度看图书管理系统最大的价值不在于它能跑起来借书还书而在于它把数据库设计、事务、分层架构、会话管理这几个JavaWeb核心知识点投影到了一个可感知的场景里。答辩前亲手把数据库里的表全删掉再重新执行一遍初始化脚本把项目从零跑通一次比任何背稿都管用。我当年吃过“只看不跑”的亏觉得代码稳了结果答辩现场抽风查了十分钟才发现是连接池配置的host写错了。建立全套初始化SQL、检查清单的习惯在这个项目里练好后面做任何JavaWeb项目都能少走很多弯路希望帮到你。本文还有配套的精品资源点击获取
返回列表