直接看代码。图统源一个能跑的书管JSP图书管理系统,核心不过三张表:books、理系users、代码borrow_records。图统源源码结构通常是书管WebContent下放jsp页面,src下放DAO和实体类。理系别被那些花哨的代码框架唬住,原生JSP+Servlet+JDBC写出来的图统源东西,反而更容易看懂图书借阅的书管底层逻辑。
先看数据库连接。理系很多教程喜欢把DriverManager.getConnection写在每个DAO方法里,代码这是图统源坑。我见过一个学生交的书管源码,每次查询都新建连接,理系压测到第50个并发就直接抛SQLException。正确的做法是用一个DBUtil类,静态代码块加载驱动,getConnection方法返回连接,close方法统一释放。简单,但能省掉后面80%的调试时间。
图书列表页book_list.jsp,核心就一段循环:
<% List<Book> books = (List<Book>)request.getAttribute("books"); for(Book b : books){ %> <tr> <td><%=b.getIsbn()%></td> <td><%=b.getTitle()%></td> <td><%=b.getAuthor()%></td> <td><%=b.getStock()%></td> </tr> <% } %> 注意那个stock字段。借书操作不是简单地把stock减一。我翻过一个开源项目,它的borrow.jsp直接执行UPDATE books SET stock=stock-1 WHERE id=?,结果两个用户同时借同一本书,库存变成-1。正确做法是在DAO层用事务:先SELECT stock FROM books WHERE id=? FOR UPDATE,判断大于0再更新,同时往borrow_records插一条记录。这三步必须在一个Connection里完成,autocommit设为false。
用户登录部分,login.jsp的form提交到LoginServlet。很多人把密码明文存进users表,这不行。至少用MD5加个盐。我见过最离谱的源码,登录判断写成if(username.equals("admin") && password.equals("123456")),硬编码在JSP里。这种代码拿去做课程设计,老师看一眼就扔垃圾桶。
借阅记录页面borrow_list.jsp有个细节:显示应还日期。源码里通常写new SimpleDateFormat("yyyy-MM-dd").format(record.getDueDate())。但数据库里存的是Timestamp,时区没处理好的话,页面上会显示成前一天。我调试过一个案例,服务器在UTC时区,用户在东八区,借书日期整整差8小时。解决办法是在JDBC连接串里加serverTimezone=Asia/Shanghai。
分页功能。原生JSP分页无非是SELECT * FROM books LIMIT ?,?,配合page和pageSize两个参数。但有些源码用ResultSet.absolute()做分页,数据量一过万就卡死。还有个常见错误:计算总页数时用totalRecords/pageSize,忘了向上取整。100条记录每页10条是10页,101条呢?得用(totalRecords+pageSize-1)/pageSize。
删除图书的操作要小心外键。borrow_records表里如果有这本书的借阅历史,直接DELETE FROM books WHERE id=?会报错。源码里应该先检查SELECT COUNT(*) FROM borrow_records WHERE book_id=?,大于0就提示“该图书有借阅记录,无法删除”。或者用软删除,加个is_deleted字段。我偏向后者,数据留着总有用。
最后说下编码问题。每个JSP页面顶部必须写<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>,Servlet里也要request.setCharacterEncoding("UTF-8")。漏掉任何一处,中文书名就会变成问号。这个坑我踩过三次,每次都是因为复制粘贴时漏了一行。
整套源码跑起来,Tomcat控制台不报错,页面上能增删改查,借还书逻辑正确,就算合格。别追求什么MVC分层完美,先让功能跑通。我见过太多人纠结于把DAO写成接口+实现类,结果连最基本的借书并发都没处理。代码是拿来用的,不是拿来看的。