ARTICLE DETAIL

资讯详情

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

Java Web图书管理系统实战:从三层架构设计到部署上线

Java Web图书管理系统实战:从三层架构设计到部署上线 每个学 Java Web 的同学基本都绕不开这个经典作品基于 javaEE 的图书管理系统。它既是课程设计里的常客也是面试时最容易暴露基本功的试金石。我之所以说“绕不开”是因为这个项目看起来只有图书增删改查和借还书但它背后牵扯的东西一点不少三层架构怎么分、Servlet 怎么写、JSP 怎么渲染、JDBC 怎么连接、事务怎么控制、Tomcat 怎么部署全都能在这一个项目里串起来。这题让我来拆我不会一上来就贴代码而是先把设计思路、表结构、请求流程讲清楚再带你一步步把功能和坑都踩明白。很多同学做课设时最迷茫的不是写不出来而是不知道代码放哪个包、请求从哪进、数据在哪一层被处理结果所有的逻辑全塞在一个 Servlet 里最后能跑但谈不上质量。这篇文章就是按一线项目开发的标准来梳理让你做完的不只是一个能交差的作业而是一套真正符合 Java EE 实践规范的图书管理系统。1. 项目整体设计与思路拆解1.1 为什么单挑“图书管理系统”来练手图书管理系统的业务模型很经典它有用户、有图书、有分类、有借还记录。这几张表之间天然存在一对多和多对多的关系比如一个分类下有多本图书一个用户能借多本书同一本书也会被多个用户在不同时间借阅。这种关系恰好覆盖了数据库设计里最核心的关联查询场景也让 Java 代码里的实体类不至于简单到没营养又不会复杂到劝退新手。更重要的是图书管理的业务流程有明确的状态流转。书从在馆到借出再到期归还库存要变记录要留过期还要能查出来。这就涉及事务、状态字段设计、业务校验等一系列真实系统里躲不开的问题。相比学生管理系统那种纯信息展示图书管理系统多了一层“借书数量不能超过库存”的业务逻辑复杂度刚好卡在课程设计最合适的区间。我做过的很多课设项目里最推荐新手认真啃的就是它。不是因为它新鲜恰恰因为它经典你能从网上找到大量参考但真正自己做一遍才知道哪些代码是抄的、哪些逻辑是理解的。能把图书管理系统从数据库到页面完整打通再去学 Spring Boot、MyBatis 这些框架你会发现很多东西都是在重复这套模式只是框架帮你省了样板代码而已。1.2 三层架构与 MVC这个项目该怎么分层很多同学一写 Java Web 就喜欢把所有代码堆在 Servlet 里一个页面一个 Servlet里面又是 JDBC 又是业务判断又是跳转。这样代码短时间能跑但只要加一个功能整个类就膨胀到几百行牵一发而动全身。图书管理系统虽然不大我还是建议严格遵守三层架构也就是表现层Web 层、业务层Service、持久层DAO。打个比方你可以把系统想象成一家餐厅JSP 页面是餐桌负责摆盘和让顾客看到菜Servlet 是服务员负责把客人的需求传到后厨再把菜端出来Service 是后厨的厨师长负责指挥“这道菜这么做”DAO 就是采购员只负责从仓库拿货。客人不会直接跑到仓库拿菜页面代码也不应该直接操作数据库。每一层只干自己那件事出了问题也好排查后厨菜做坏了你总不能怪采购员的进货单写得不好。对应到项目结构上我习惯分成这样几个包com.library.entity放实体类com.library.dao放数据库操作com.library.service放业务逻辑com.library.web放 Servlet 和过滤器。JSP 页面统一放在 WebRoot 或 webapp 目录下页面里尽量用 EL 表达式和 JSTL 标签别写一堆脱离 HTML 框架的 Java 脚本。MVC 在这个架构里的角色很明确JSP 负责 ViewServlet 负责 ControllerService DAO 合起来是 Model。Servlet 收到请求后调 ServiceService 返回结果Servlet 再把结果塞到 request 域里转发给 JSPJSP 渲染完返回浏览器。这条链路看着长但每一步职责单一项目规模越往大走越能体会到它的价值。2. 开发环境准备与项目搭建2.1 工具清单与版本搭配别上来就踩坑我先说结论做这个项目最稳的组合是JDK 8 Tomcat 8.5 Eclipse/IDEA MySQL 5.7 或 8.0。为什么不用最新版因为 Java EE 的技术栈在 2019 年后经历了一个历史性变化Java EE 8 及之前的规范包名都叫javax.servlet.*但 2019 年 Oracle 把 Java EE 交给 Eclipse 基金会后更名为 Jakarta EE包名也换成了jakarta.servlet.*。你要是装了 Tomcat 10再用以前网上抄的javax.servlet代码一启动就直接报找不到类特别容易让新手怀疑人生。所以课程设计阶段我不建议追新就用 Tomcat 8.5 配上javax包体系的代码网上资料最多遇到问题最好搜。等以后学 Spring Boot 了内置的 Tomcat 版本你自己能控制到时候再逐步切到 Jakarta 体系也不迟。开发工具方面IDEA 肯定是最省心的社区版免费也够用。不过我也注意到很多同学用 VS Code 写 Java EE因为轻量、开箱快。这里简单提一下 VS Code 配置 Java EE 环境的关键点你只装一个 Java Extension Pack 是不够的代码补全和报错提示有了但跑 Web 项目还需要配置 Tomcat。VS Code 里装完 Tomcat for Java 插件以后需要你在设置里指定 Tomcat 安装路径然后在项目里生成.vscode/launch.json配置好启动类否则 F5 都按不出来。2.2 Maven 还是非 Maven我的建议课设阶段的图书管理系统我见到最多的做法是不用 Maven直接把 Jar 包放到WEB-INF/lib下。说实话这对新手更友好因为你能亲眼看到 MySQL 驱动 JAR 放在哪个位置路径不对也更容易理解。但如果你想从这个项目开始就接触正规开发流程那就上 Maven用pom.xml统一管依赖。用 Maven 的pom.xml核心依赖大概是这样的dependencies dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjsp-api/artifactId version2.0/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdcommons-dbutils/groupId artifactIdcommons-dbutils/artifactId version1.7/version /dependency /dependencies这里面最容易出错的是servlet-api和jsp-api一定要加scopeprovided因为 Tomcat 本身已经有这两个类了。如果你用默认的 compile 作用域打包部署后可能出现类冲突、方法找不到之类的诡异问题。我在实际项目里就见过有人把 servlet-api 打进了 war 包结果一到 Tomcat 上启动就报 NoSuchMethodError去掉依赖重新打包就好了。2.3 项目目录结构与 web.xml 配置标准的 Java EE 动态 Web 项目目录长这样library-system/ ├── src/ │ └── main/ │ ├── java/ │ │ ├── com/library/entity/ │ │ ├── com/library/dao/ │ │ ├── com/library/service/ │ │ └── com/library/web/ │ └── resources/ │ └── db.properties └── webapp/ ├── static/ ├── WEB-INF/ │ ├── web.xml │ └── lib/ └── index.jspweb.xml在 Servlet 3.0 之前是必须手动配置 Servlet 映射的3.0 之后可以用WebServlet注解省掉一部分配置但我建议还是保留web.xml至少把欢迎页和编码过滤器写在里面方便别人接手时一眼看懂项目入口。最常见的欢迎页配置是这样welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list注意index.jsp一般不要直接放查询全部图书的逻辑而是做一个转发根据用户是否登录跳到登录页或主页面。这样后面加权限控制时会方便很多。3. 数据库设计与核心逻辑实现3.1 表结构设计从用户到借阅记录图书管理系统的数据库设计我建议至少建四张表用户表、图书表、图书分类表、借阅记录表。很多课设版本会砍掉分类表把分类直接做成图书表里的一个字符串字段。这样能少写几个接口但借阅记录查询、按分类统计的时候就非常痛苦。我还是建议老老实实建四张表。用户表t_user字段类型说明idint主键自增usernamevarchar(50)用户名唯一passwordvarchar(64)密码MD5 加密后存储realnamevarchar(50)真实姓名选填roletinyint角色1 管理员2 普通用户create_timedatetime创建时间图书表t_book字段类型说明idint主键自增book_namevarchar(200)书名authorvarchar(100)作者publishervarchar(100)出版社category_idint分类外键totalint总册数stockint当前可借库存create_timedatetime入库时间分类表t_category字段类型说明idint主键自增category_namevarchar(50)分类名称借阅记录表t_borrow字段类型说明idint主键自增user_idint借书用户book_idint被借图书borrow_timedatetime借出时间return_timedatetime实际归还时间deadline_timedatetime应还时间一般借期30天statustinyint0 借出中1 已归还我特意把图书表的total和stock分开总册数和当前库存是两个概念。有人借走一本书stock减一还回来stock加一。total永远不变。这样统计馆藏总量和实时库存都方便也避免把“馆藏多少本”和“现在能借几本”混成一个字段。3.2 数据库初始化脚本注意 keyword 加反引号MySQL 建表脚本我贴一段核心的特别提醒一下user是 MySQL 的保留字直接建表会报语法错误。所以我建议表名都用t_前缀字段名也别叫order、group、desc这种保留字省得每次写 SQL 都得加反引号。CREATE DATABASE IF NOT EXISTS library_db DEFAULT CHARSET utf8mb4; USE library_db; CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, realname VARCHAR(50), role TINYINT DEFAULT 2, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE t_category ( id INT PRIMARY KEY AUTO_INCREMENT, category_name VARCHAR(50) NOT NULL ); CREATE TABLE t_book ( id INT PRIMARY KEY AUTO_INCREMENT, book_name VARCHAR(200) NOT NULL, author VARCHAR(100), publisher VARCHAR(100), category_id INT, total INT DEFAULT 1, stock INT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_book_category FOREIGN KEY (category_id) REFERENCES t_category(id) ); CREATE TABLE t_borrow ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, book_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP, return_time DATETIME, deadline_time DATETIME, status TINYINT DEFAULT 0, CONSTRAINT fk_borrow_user FOREIGN KEY (user_id) REFERENCES t_user(id), CONSTRAINT fk_borrow_book FOREIGN KEY (book_id) REFERENCES t_book(id) );建表脚本跑完之后记得先插入一个初始管理员账号。常见做法是手动把管理员密码做成 MD5 后写入 SQL比如admin / 123456。我建议不要把明文密码写在页面上也不要直接在 SQL 里写明文后面代码里统一做加密这样至少在校验流程上是完整的。3.3 JDBC 工具类与数据库连接池开发环境里最直接的 JDBC 连接可以用DriverManager但真实项目里没人这么干因为频繁创建和关闭连接对数据库压力很大。这里我建议用 DBCP 或者 C3P0 连接池。课设里用 C3P0 的相对多配置简单文档也多。先说最简单的连接配置我习惯引入db.properties文件jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/library_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456 jdbc.initialSize5 jdbc.maxActive20注意 MySQL 8.x 的驱动类是com.mysql.cj.jdbc.DriverMySQL 5.x 是com.mysql.jdbc.Driver。你用 8.0 的驱动连 5.7 的数据库没问题但反过来就报错。另外连接 URL 里serverTimezone这个参数在 8.0 驱动下是必填的不加的话你执行 SQL 时大概率会报时区相关的异常。JDBC 工具类核心代码如下这里我同时用到了try-with-resourcesJDK 7 以上都能用public class DBUtil { private static String driver; private static String url; private static String user; private static String password; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); driver props.getProperty(jdbc.driver); url props.getProperty(jdbc.url); user props.getProperty(jdbc.username); password props.getProperty(jdbc.password); Class.forName(driver); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, password); } public static void close(ResultSet rs, Statement stmt, Connection conn) { if (rs ! null) { try { rs.close(); } catch (SQLException ignored) {} } if (stmt ! null) { try { stmt.close(); } catch (SQLException ignored) {} } if (conn ! null) { try { conn.close(); } catch (SQLException ignored) {} } } }提示使用连接池后conn.close()不是真正关闭连接而是把连接归还到池里复用所以规范地用close()反而能让连接更安全这一点很多新手理解反了。3.4 密码加密与 SQL 注入预防登录功能最大的两个坑一个是明文存密码一个是拼接字符串 SQL。明文存密码的问题不用多说数据库一旦泄露所有账号直接完蛋。正确做法是 MD5 加盐。MD5 本身虽然现在不算强加密算法但作为课设演示足矣。加盐的意思是在密码后面拼一段固定随机串再算 MD5避免两个用户密码相同导致摘要相同。注册时存的是MD5(password salt)登录时取出这个用户对应的盐重新计算再比对。密码加密工具类很简单public class MD5Util { private static final String SALT Library2024; public static String encrypt(String password) { String target password SALT; try { MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(target.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { String hex Integer.toHexString(0xff b); if (hex.length() 1) { sb.append(0); } sb.append(hex); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } }SQL 注入预防就一个原则永远不要用字符串拼接 SQL永远用 PreparedStatement。比如根据用户名查用户的 SQL写成SELECT * FROM t_user WHERE username username 就是在给黑客递刀子。输入 OR 11直接绕过登录。用PreparedStatement的?占位符参数由数据库驱动处理转义就安全得多。这个习惯从做课设开始就要养成等到工作里再改就很痛苦了。4. 功能模块拆解与页面实现4.1 登录认证与 Session 管理登录流程很好设计用户提交表单Servlet 拿到用户名和密码后调 Service 查询Service 里对密码做 MD5再和数据库比对。比对成功就把用户对象放进 Session跳转到主页失败就返回登录页并带上错误信息。不过图书管理系统有个很常见的问题——用户不登录也能直接访问管理页面。解决方法是加一个 Filter 做登录拦截WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); String uri req.getRequestURI(); // 登录页、登录接口、静态资源不拦截 if (uri.endsWith(/login.jsp) || uri.endsWith(/login) || uri.contains(/static/)) { chain.doFilter(request, response); return; } if (session null || session.getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }注意req.getSession(false)和req.getSession()的区别是前者在 Session 不存在时返回 null后者会强行创建一个新 Session。做登录拦截时用前者更合理因为你不需要在没有登录之前就自动创建 Session白白浪费服务器内存。4.2 图书信息管理列表、分页、搜索图书列表是整个系统里访问最频繁的页面我建议从这一步开始就做分页别把所有数据一次性查出来。如果馆藏图书几千本一次全查不仅慢页面渲染也会卡。分页的核心就是两个参数当前页码pageNo、每页条数pageSize。SQL 就一句LIMIT offset, pageSize其中offset (pageNo - 1) * pageSize。查询总数用SELECT COUNT(*) FROM t_book然后在前端页码条里循环展示页码。分页的 DAO 方法大致长这样public ListBook findByPage(int pageNo, int pageSize, String keyword) throws SQLException { String sql SELECT * FROM t_book WHERE book_name LIKE ? ORDER BY id DESC LIMIT ?, ?; ListBook list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setInt(2, (pageNo - 1) * pageSize); ps.setInt(3, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { Book book new Book(); book.setId(rs.getInt(id)); book.setBookName(rs.getString(book_name)); // 省略字段填充 list.add(book); } } } return list; }图书新增和编辑的表单就没什么好说的了需要注意的就是在服务端做校验书名必填、作者长度限制、库存不能小于总册数。前端表单校验能提升体验但它不是安全边界一样能被绕过服务端校验才是真正的关卡。另外一个细节是库存字段编辑图书时如果直接把stock改成新值会覆盖掉当前借出状态下剩余的库存。正确做法是如果只是修改基本信息stock不参与更新真要调整库存单独写一个“入库”功能让管理员输入增加的数量这样逻辑清楚且不容易误操作。4.3 借书、还书与事务控制借书还书是整个系统里最能体现事务价值的地方。借一本书至少涉及两件事往t_borrow插入一条记录、把t_book里的stock减一。如果第一步成功、第二步失败就会出现“记录显示借了但书还在馆”的状态。这两步必须放在同一个数据库连接、同一个事务里要么都成功要么都回滚。Java EE 里手动开事务很简单关键是必须用同一个Connection对象Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 校验库存 String checkSql SELECT stock FROM t_book WHERE id ? FOR UPDATE; // 2. 更新库存 String updateSql UPDATE t_book SET stock stock - 1 WHERE id ? AND stock 0; // 3. 插入借阅记录 String insertSql INSERT INTO t_borrow(user_id, book_id, deadline_time) VALUES (?, ?, ?); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } }这里的FOR UPDATE是行级锁防止两个人同时借同一本书的最后库存。还有一个小技巧第二步的更新 SQL 里带了AND stock 0条件如果影响行数是 0就说明库存不足或者书不存在直接在代码里返回“借书失败”。这种在 SQL 里做条件更新比先查询再判断更安全因为查询和更新之间有并发操作的空隙。还书同理更新t_borrow的状态和实际归还时间再把t_book.stock加一。逾期判断可以在还书时用当前时间和deadline_time比较如果当前时间更晚就在页面提示“借阅超期请尽快归还”。4.4 JSP 页面渲染拒绝 Scriptlet 满天飞图书列表页 JSP 如果写满% for (...) { %代码会很难维护我强烈建议用 EL 表达式和 JSTL 标签。在pom.xml引入 JSTL 后页头引入标签库% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %列表循环是这样table classtable table-bordered thead tr thID/th th书名/th th作者/th th出版社/th th分类/th th总册数/th th可借数/th th操作/th /tr /thead tbody c:forEach items${page.list} varbook tr td${book.id}/td td${book.bookName}/td td${book.author}/td td${book.publisher}/td td${book.categoryName}/td td${book.total}/td td${book.stock}/td td a href${pageContext.request.contextPath}/book/edit?id${book.id}编辑/a a href${pageContext.request.contextPath}/book/delete?id${book.id} onclickreturn confirm(确认删除该图书吗)删除/a /td /tr /c:forEach /tbody /table注意book.categoryName这个属性Book实体里需要有一个categoryName字段在 DAO 查询时通过连接查询把分类名称带出来。很多新手只想在列表页显示分类名又不建categoryName字段结果只能在 JSP 里再查一次数据库一个页面 N 条 SQL这就是传说中的 N1 查询性能很差。分页条的 JSTL 写法也不复杂就是生成一组“上一页、页码、下一页”的链接。关键是地址里要带上pageNo和keyword两个参数这样翻页时搜索条件才不会丢。5. 部署上线与踩坑实录5.1 从 Eclipse/IDEA 到 Tomcat 的 war 包部署本地开发时 IDE 通常会自动部署到 Tomcat但课程设计交作业或给别人演示时往往需要打一个 war 包。用 Maven 打包很简单运行mvn clean package然后把target目录下的.war文件扔到 Tomcat 的webapps目录里启动 Tomcat 后它会自动解压部署。不用 Maven 的话打包路径是这样的在 Eclipse 里右键项目选择 Export选 WAR file输出到 Tomcat 的webapps目录下。这中间最容易栽跟头的就是WEB-INF/lib下缺 MySQL 驱动 JAR导致本地运行好好的一换环境就报ClassNotFoundException: com.mysql.jdbc.Driver。解决办法是去 MySQL 官网下载对应版本的 Connector/J把 JAR 同时放进项目的webapp/WEB-INF/lib和 Tomcat 的lib目录下双保险。部署后如果想要外部机器能访问注意服务器防火墙要放行 8080 端口。云服务器还要在安全组里把 TCP 8080 加进白名单这两处不配好别人访问你的 8080 就一直是超时或拒绝连接。5.2 常见问题速查表这些坑我全都踩过报错现象根本原因解决办法ClassNotFoundException: com.mysql.jdbc.Driver缺 MySQL 驱动 JAR或驱动类名与版本不匹配添加对应版本的 Connector/J 依赖JSP 页面中文全是????页面编码、数据库连接 URL、MySQL 表字符集不统一统一用 UTF-8连接 URL 加characterEncodingutf8请求路径 404但代码没问题Servlet 映射路径写错或项目访问路径带 Context Path用${pageContext.request.contextPath}拼绝对路径每次启动 Tomcat 都报端口被占用8080 端口被其他进程占用杀掉占用进程或改 conf/server.xml 中的端口执行 SQL 报Unknown column字段名拼写错误或表里压根没这列SQL 打印出来和表结构对一遍登录成功但马上又跳回登录页Session 存取不一致或 Filter 拦截顺序有问题确认登录成功是否session.setAttributeFilter 是否放行相关请求页面报 500日志里有Cannot call getNString()实体类 getter/setter 和数据库字段映射不上检查实体类字段名和数据库列名是否一致部署到 Tomcat 启动报NoSuchMethodErrorservlet-api 重复打包进 war把 servlet-api 和 jsp-api 的 scope 改成 provided借书后库存没变更新库存的 SQL 影响行数为 0事务回滚打印 SQL 和参数确认book_id是否传对这里我最想单独拎出来说的是中文乱码。乱码问题表面上只有一个现象根源却能分三层第一层是 JSP 页面本身的编码第二层是 Tomcat 接收请求参数的编码第三层是 MySQL 表结构和服务端连接串的编码。你光改一个地方问题依旧。所以我的习惯是三步同时做JSP 页面头部写% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%Tomcat 的server.xml里给 Connector 加URIEncodingUTF-8然后所有 JSP 提交表单前再用一个全局过滤器统一设置request.setCharacterEncoding(UTF-8)。三处都齐了乱码基本绝迹。5.3 性能与安全层面的几个进阶建议图书管理系统做到能跑只是第一步想让它看起来像“专业系统”还有几件事值得加上。第一件是统一异常处理和错误页面。如果访问一个不存在的图书 IDDAO 查出来是 nullService 层不判断直接往下走JSP 里就会报空指针。在web.xml里配置一下错误页error-page error-code404/error-code location/404.jsp/location /error-page error-page error-code500/error-code location/500.jsp/location /error-page至少用户看到的是友好提示而不是一屏看不懂的堆栈信息。第二件是操作日志。图书管理系统的核心操作无非就是登录、新增图书、修改库存、借书、还书。如果能在 Service 层把这些动作记录到一张t_log表里记录操作人、操作类型、目标对象、时间那这个系统从“课设作业”到“可交付项目”的质感就完全不一样了。做这个功能本身不复杂一个 LogDao 加几个 service 方法而已但对整个系统的完整度提升很值。第三件是日志打印。不要用System.out.println去调试课设阶段还无所谓代码规模再大一点就完全失控。建议用一个简单的 Slf4j Log4j或者退一步用java.util.logging关键节点打日志见人见字排查问题会高效很多。6. 这套项目做完之后怎么进一步扩展如果你按照上面的思路把这套图书管理系统完整写一遍并且把借还书事务、分页搜索、登录过滤器、密码加密这些细节都消化掉那么你放心Spring Boot 或 SSM 框架的学习成本会低一大截。因为框架再强大本质上只是在做同样的事Servlet 换成 ControllerJDBC 换成 MyBatisJSP 换成 Thymeleaf 或 Vue 前端分离但请求流程和业务逻辑的思考方式是一样的。我个人经验里最推崇的做法是第一遍练习时老老实实用 Servlet JSP JDBC 手写不借助任何框架等你对所有环节都理解了再对比着看 Spring MVC 的 DispatcherServlet 做了什么MyBatis 的 SqlSession 又在 JDBC 之上封装了什么。这种“先手写、再框架”的顺序比较反直觉但效果通常比一上来就学框架扎实得多。很多人学 Spring Boot 学得云里雾里就是因为不清楚底层到底发生了什么遇到问题只能瞎猜。最后再分享一个小技巧你可以在图书表里加一个cover_url字段把本地图片上传功能也做进去。虽然这已经超出了“图书管理系统”的课设基本要求但上传下载这个能力在很多真实系统里都躲不开。用 fileupload 组件或者直接读 MultipartRequest 实现一把你会发现它比想象中简单而且对 Java Web 里 IO 的理解又深了一层。往后的路就是从这个小系统开始一步一步往更真实、更完整的系统上迈。
返回列表