ARTICLE DETAIL

资讯详情

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

JavaEE课程设计实战:从零搭建图书管理系统全流程指南

JavaEE课程设计实战:从零搭建图书管理系统全流程指南 期末又到了又到了一年两度JavaEE课程设计集中交作业的季节。扫一眼各技术社区的热搜词图书管理系统稳坐前排后面还跟着一串vscode配置javaee语言环境javaee课程设计基于web的图书管理系统之类的高频搜索。说实话这个题目被选烂了但被选烂恰恰说明它值得做——它踩中了JavaEE学习路径上几乎所有关键节点Servlet生命周期、JSP渲染、JDBC操作数据库、会话跟踪、过滤器拦截、分层设计。把这些东西串起来通一遍你对JavaEE的认知会从背了一堆概念但不知道怎么落地变成哦原来它们是这么配合干活的。这篇东西不是教科书复述我尽量按照实际动手的顺序来讲穿插一些我在帮人调这个项目时经常见到的坑和对应的处理思路。目标是让你跟着走完能自己从头搭出一个能跑、能演示、能过答辩的完整系统。1. 为什么图书管理系统是JavaEE课程设计的标准答案先聊点务虚的但这个问题想清楚了后面写代码会顺畅很多。1.1 它覆盖了一门JavaEE课程的核心知识面回想一下JavaEE课程大纲里那些章节Servlet、JSP、JDBC、Session/Cookie、Filter、Listener、MVC分层。这些知识点单独拎出来每一个都不难难的是把它们组合进一个完整的程序里。图书管理系统刚好是一个麻雀虽小五脏俱全的载体登录注册要用到Servlet处理表单请求、Session维持登录态、Filter做访问控制、MD5或SHA-256做密码摘要存储。图书的增删改查要走JDBC连接数据库、PreparedStatement防注入、结果集封装成对象、再把数据传给JSP展示。借书还书涉及事务边界、库存判断、外键关联这些是数据库设计的核心。分页查询要写LIMIT语句、计算总页数、处理页码参数这是以后做任何管理后台都会遇到的通用需求。你把这些功能点挨个实现了这门课的核心实验目标基本就达成了。1.2 业务需求边界清晰不需要领域知识图书管理系统的业务规则足够简单有书、有人、有人借书、有人还书。不会像电商系统那样牵扯到购物车、库存锁定、支付回调、优惠叠加这些复杂的业务概念。对于课程设计来说需求边界越清晰你越能把注意力集中在技术怎么实现上而不是陷在业务逻辑里出不来。1.3 演示效果直观答辩好讲图书管理系统最终呈现出来的界面和用户操作路径非常直白登录进去能看到书列表点借阅就能借到书管理员能新增删除图书。这种可视化程度高的项目答辩的时候你不用费太多口舌解释这个项目到底干了什么评委自己点两下就能看懂你只需要把技术要点讲清楚就行。2. 系统功能清单与数据模型的确定动手写代码之前先把需求定死。我的习惯是先用一张表把功能列清楚避免写着写着需求蔓延。2.1 用户侧和管理侧的功能划分一个标准的图书管理系统用户角色至少分两种普通读者和管理员。不同角色看到的功能入口不一样角色核心功能普通读者注册、登录、浏览图书列表、按书名/作者搜索、借书、还书、查看个人借阅历史管理员登录、图书新增/编辑/删除、查看全部借阅记录、管理读者账号可选这里有一个常见问题很多人设计系统的时候上来就做管理员功能不管读者功能。其实按照课程设计的评分标准来看读者借还书的完整流程往往才是重点因为这是系统最核心的业务闭环。管理员界面可以做得朴素一点但借还书流程必须完整走通。2.2 三张核心表的设计与字段说明根据上面的功能划分数据库最少需要三张表用户表、图书表、借阅记录表。用户表users字段名类型说明user_idINT 自增主键用户IDusernameVARCHAR(50) 唯一登录用户名passwordVARCHAR(64)密码的SHA-256摘要不是明文nicknameVARCHAR(50)显示昵称roleTINYINT1表示管理员0表示普通读者create_timeDATETIME注册时间图书表books字段名类型说明book_idINT 自增主键图书IDbook_nameVARCHAR(100)书名authorVARCHAR(50)作者publisherVARCHAR(100)出版社categoryVARCHAR(50)分类如计算机文学total_stockINT总库存current_stockINT当前可借数量create_timeDATETIME入库时间借阅记录表borrow_records字段名类型说明record_idINT 自增主键借阅记录IDuser_idINT借书人关联users表book_idINT图书关联books表borrow_timeDATETIME借出时间due_timeDATETIME应还时间通常借出时间30天return_timeDATETIME NULL实际归还时间NULL表示未还statusTINYINT0借出中1已归还2已逾期关于current_stock字段有一个设计上的权衡理论上可以通过总库存减掉借出中记录数动态算出可借数量不必单独存一个字段。但课程设计阶段我建议还是保留这个字段因为借书时直接UPDATE books SET current_stock current_stock - 1 WHERE book_id ? AND current_stock 0是最简单可靠的库存扣减方式写起来清晰答辩也好解释。2.3 为什么不建议把表拆得太碎见过有人把图书分类单独建一张表、把出版社单独建一张表理由是符合第三范式。想法没错但对课程设计来说多表关联JOIN会让JSP页面取值变得复杂而书名的查询速度在这个数据量级下根本感觉不出来差别。个人建议分类直接存字符串出版社直接存字符串把精力留给借还书这个核心闭环。3. JavaEE技术栈选型ServletJSPJDBC的组合逻辑选型是每个JavaEE项目开始前绕不开的问题而且JavaEE体系的选项特别多。我先说明我推荐什么再解释为什么。3.1 三个组件的分工为什么这个组合最合适推荐组合Servlet JSP JDBC Druid连接池部署在Tomcat上IDE用IDEA。Servlet负责接收HTTP请求、调用业务逻辑、根据结果决定页面跳转方向。它是整个系统的交通枢纽。JSP负责展示动态数据本质是一个被容器翻译成Servlet再执行的模板引擎。JSP里尽量只做取数据和循环展示不写业务逻辑。JDBCJava访问数据库的标准接口配合连接池复用数据库连接避免每次请求都重新创建连接。Druid连接池阿里巴巴开源的数据库连接池监控功能完善、性能好配置也比C3P0简单。课程设计用Druid还有一个额外的好处它自带的监控页面可以在答辩时展示属于加分项。这三个组件的配合方式用一句话概括浏览器发请求到ServletServlet调用DAO层访问数据库JDBC然后把结果放到request域或session域里forward到JSP渲染成HTML返回给浏览器。这样一来每个请求从进入到返回的路径都很清楚调bug的时候顺着这条链路走就行。3.2 连接池参数怎么配以Druid为例Druid用起来不复杂一般通过properties文件配置然后在项目启动时初始化一次。下面是一个课程设计够用的配置方案druid.propertiesdriverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/library_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue usernameroot password你的数据库密码 initialSize5 maxActive20 maxWait5000解释几个关键参数initialSize5连接池启动时预创建5个连接避免第一个请求来临时再去初始化连接提高首请求速度。maxActive20连接池最多同时提供20个连接。课程设计的并发量很低20足够用了设得太大会浪费数据库资源。maxWait5000当连接池没有空闲连接时最长等待5秒。超过这个时间就抛异常防止请求无限阻塞。URL里的characterEncodingutf8和serverTimezoneAsia/Shanghai这两个参数特别容易漏漏了会出现中文乱码和日期时间差8小时的问题。建议直接复制上面这段配置。获取连接的工具类可以大致写成这样public class JdbcUtils { private static DruidDataSource dataSource; static { try (InputStream is JdbcUtils.class.getClassLoader().getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(is); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, Statement stmt, ResultSet rs) { if (rs ! null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt ! null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }注意close方法的处理连接池提供的连接调用close不是真的关闭物理连接而是把连接归还到池子里下次还能复用。这是连接池的基本工作方式答辩时很可能会被问到。3.3 为什么不在课程设计里引入Spring/MyBatis现在企业里做JavaWeb开发基本是Spring Boot MyBatis/JPA的天下很多同学会有疑问课程设计能不能直接上Spring Boot我理解这个想法但分情况如果你的课程大纲明确要求用JavaEE体系也就是Servlet/JSP这套那就老老实实用传统方式因为评分标准可能包含使用了Servlet手动实现过滤器传统JDBC操作这些硬性指标。如果大纲没有限制技术栈你想用Spring Boot也可以但要注意Spring Boot会替你封装掉大量底层细节你反而讲不清楚JavaEE到底是怎么回事。折中方案先把ServletJSPJDBC版本跑通有余力了再横向对比如果用Spring Boot这些代码会变成什么样这对自己理解框架的价值大有好处。我遇到过不少学生拿着Spring Boot项目去答辩JavaEE课设被评委一问那你说说Servlet在你这项目里怎么用的就愣住了。这属于典型的工具用错了场景。4. 开发环境搭建设好环境再动手能省一半时间这个环节写在功能实现之前不是凑篇幅。我敢说课程设计翻车案例里有三成以上是环境问题导致的很多人代码写完了项目部署不到Tomcat上一直在报各种莫名其妙的错。4.1 JDK Tomcat IDEA的版本搭配建议版本匹配是环境配置里最关键的一个点不匹配就会出现各种兼容性错乱。建议直接采用下面这个组合组件推荐版本说明JDKJDK 8 或 JDK 11绝大多数教材和生产环境仍以JDK 8为主稳定且资料多TomcatTomcat 9.x对应Servlet 4.0规范兼容JDK 8/11比Tomcat 10配置简单IDEIntelliJ IDEA Community社区版免费功能足够课程设计若要更好Web支持可用Ultimate版MySQLMySQL 5.7 或 MySQL 8.0推荐MySQL 8.0记得URL里加serverTimezone数据库驱动mysql-connector-java 8.0.x版本要和MySQL主版本匹配一个容易踩的坑Tomcat 10之后将javax.servlet包改名成了jakarta.servlet如果你下载了Tomcat 10之前所有教材里的import javax.servlet.*代码全部会编译报错。课程设计阶段别去趟这个浑水老老实实用Tomcat 9。4.2 VS Code配置JavaEE语言环境的完整路径现在不少同学用VS Code而不是IDEA因为免费、轻量。但VS Code不是开箱即用的Java IDE需要配置的东西比较多。根据我实际配过的经验完整路径如下第一步安装基础扩展。打开VS Code扩展面板搜索并安装这几个必装扩展Extension Pack for Java微软官方出品的Java扩展包一次装完语言支持、调试器、Maven支持Tomcat for Java用于在VS Code里直接启动和调试TomcatPrettier - Code formatter代码格式化可装可不装第二步确认JDK配置。按CtrlShiftP输入Java: Configure Java Runtime在打开的页面里检查JDK路径是否指向你本机安装的JDK。如果这里显示空白说明VS Code没找到JDK需要手动添加JDK安装路径注意是JDK根目录不是bin目录。第三步配置Maven可选。Extension Pack for Java内置了Maven支持但会使用VS Code自带的Maven版本下载依赖可能在国内网络环境下比较慢。建议在settings.json里指定本机Maven仓库和阿里云镜像方法是在Maven的settings.xml里加入mirror idaliyunmaven/id mirrorOf*/mirrorOf namealiyun maven mirror/name urlhttps://maven.aliyun.com/repository/public/url /mirror第四步配置Tomcat。安装Tomcat for Java扩展后VS Code底部状态栏会出现一个Tomcat Server的入口或通过命令面板输入Tomcat: Add选择本机Tomcat安装目录即可。添加后可以右键启动Tomcat启动日志会输出到VS Code终端里。第五步部署Web项目。VS Code对传统的WAR包部署支持有限比较顺手的方式是用Maven构建出一个WAR包通过Tomcat扩展部署或者直接使用Tomcat: Open选择你的项目文件夹进行热部署。对于课程设计这种小项目我建议直接用Maven的package打war包复制到Tomcat的webapps目录下启动Tomcat会自动解压部署。这种方式最直观、最不容易出幺蛾子。4.3 VS Code相对IDEA最大的区别在于调试入口IDEA里调试Web项目直接配置一个Tomcat运行配置然后点Debug按钮就行。VS Code里则需要通过launch.json配置调试器而且要先通过Tomcat扩展启动Tomcat让Tomcat运行在调试模式下再用VS Code的Java调试器附加到Tomcat进程上。具体做法通过Tomcat扩展启动Tomcat时在Tomcat启动命令里加上JPDA_ADDRESS8000Tomcat的JPDA调试端口默认是8000。在VS Code里创建launch.json添加一个类型为java、模式为attach的调试配置端口填8000host填localhost。先在VS Code里启动Tomcat扩展的调试模式再运行Java调试器附加进程之后就能在VS Code里给Servlet代码打断点了。这一段配置比较复杂第一次弄可能有点折腾。但如果你的主力编辑器是VS Code值得花半小时搞定调试功能后面排错的效率会高很多。5. 登录认证与会话管理整个系统的安全地基图书管理系统里登录是第一个和安全沾边的功能也是几乎所有JavaEE项目的入门项目。认真实现一遍登录认证和会话管理你会彻底理解Session、Cookie、Filter这几个东西到底在什么场景下起作用。5.1 登录流程前端表单、Servlet接收、数据库校验先理清一次登录请求的完整链路用户在login.jsp填写用户名和密码点提交表单以POST方式发送到/login这个URL。服务器端LoginServlet收到请求先从request里取出username和password参数。LoginServlet调用UserDao的findByUsernameAndPassword方法通过JDBC到数据库里查是否有匹配的记录。如果查到把用户信息写入Session然后重定向到首页或者图书列表页如果查不到响应里带上错误信息返回登录页重新登录。这个过程的关键在JSP写表单的地方。注意login.jsp里form标签必须写methodpost否则用户名密码会通过URL传递出现在浏览器地址栏里既不安全又容易被服务器的访问日志记录。对于课程设计来说这是一个细节加分的点。LoginServlet的核心逻辑大致如下WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { HttpSession session req.getSession(); session.setAttribute(loginUser, user); resp.sendRedirect(req.getContextPath() /book/list); } else { req.setAttribute(errorMsg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }有个细节req.setCharacterEncoding(UTF-8)必须放在getParameter之前调用否则POST请求里的中文参数会乱码。这个跟Tomcat服务器的默认编码有关Tomcat默认对POST表单使用ISO-8859-1解码只有手动指定UTF-8才能正确解析中文。5.2 Session与Filter配合登录拦截器的实现解决了登录问题接下来自然要解决未登录用户不能访问页面的问题。比如你不希望别人没登录就直接输入/book/list访问图书列表。这里就要用Filter了。实现一个LoginFilter拦截所有需要登录才能访问的请求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; String uri req.getRequestURI(); // 放行登录页、登录请求、注册页、静态资源 if (uri.endsWith(/login.jsp) || uri.endsWith(/login) || uri.endsWith(/register.jsp) || uri.endsWith(/register) || uri.contains(/css/) || uri.contains(/js/) || uri.contains(/images/)) { chain.doFilter(request, response); return; } HttpSession session req.getSession(false); // 注意不要创建新Session if (session ! null session.getAttribute(loginUser) ! null) { chain.doFilter(request, response); } else { resp.sendRedirect(req.getContextPath() /login.jsp); } } }这里重点说一下req.getSession(false)这个陷阱。getSession()如果不带参数会在没有Session时自动创建一个新Session这会导致一个问题如果用户从来没登录过你本来想把他重定向到登录页结果getSession()反而给他创建了一个Session刷了一堆无用的Session记录在服务器里。所以做登录校验时一定要用getSession(false)没有Session就返回null干干净净地走重定向逻辑。Filter还有一层价值以后你想往系统里加操作日志记录接口响应时间统计都可以在Filter的chain.doFilter前后插入代码。理解一次Filter的执行机制后面这些扩展都变得很简单。5.3 密码不能明文存加减盐的SHA-256处理课程设计常见的偷懒写法是把密码直接存成明文数据库一打开全是一目了然的密码。这种做法拿到答辩场上容易被扣分。这不是让你做一个完整的密码加密系统而是至少要展示我知道密码不能明文存储这个安全常识。推荐做法注册时对密码做SHA-256加盐摘要登录时用同样的算法再算一次摘要去比对。public class PasswordUtil { public static String sha256WithSalt(String password, String salt) { try { MessageDigest md MessageDigest.getInstance(SHA-256); md.update((salt password).getBytes(StandardCharsets.UTF_8)); byte[] digest md.digest(); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } public static String generateSalt() { byte[] bytes new byte[16]; new SecureRandom().nextBytes(bytes); return new String(HexFormat.of().formatHex(bytes)); } }salt可以用用户注册时间或随机字符串生成存储时和密码摘要一起放在users表里。登录校验时取出该用户的salt用salt输入的密码重新计算摘要比对两个摘要字符串是否相等就完成了。这个过程在答辩时讲出来评委一向点头。6. 图书CRUD与借阅归还的关键实现思路登录绕完之后就进入系统的核心业务功能了。这部分代码量最大但实现思路是相通的掌握套路就能举一反三。6.1 图书新增/编辑/删除的实现要点图书管理说白了就是一张表的基础增删改查套路固定DAO层写操作数据库的方法Servlet层接收参数、调用DAO、根据结果跳转JSP层负责展示。图书删除有一个特别容易被忽视的问题外键约束。如果一个图书已经被借阅记录表引用了直接执行DELETE FROM books WHERE book_id ?会报外键约束错误如果建表时声明了外键。处理思路有两个逻辑删除给books表加一个is_deleted字段删除操作只把这个字段置为1查询时统一过滤。这是企业级系统的常规做法好处是保留历史记录坏处是每一条SQL都要多写一个WHERE is_deleted 0。物理删除时先判断删除前查一下borrow_records里有没有该图书未归还的记录有的话禁止删除并提示该图书存在未归还的借阅记录无法删除。课程设计的数据量小、借阅记录也不多我建议直接用第二种方式代码写起来更直观答辩也容易讲清楚业务逻辑。新增和编辑的页面可以共用一个book_form.jsp通过隐藏字段区分是新增还是编辑input typehidden namebookId value${book.bookId ! null ? book.bookId : } /Servlet里通过判断bookId是否为空来分流是新增还是更新if (bookId null || bookId.isEmpty()) { bookDao.insert(book); } else { bookDao.update(book); }这样少写一个页面维护起来也方便。代码里的三元表达式在JSP里用多了会影响可读性建议把逻辑放到Servlet里判断JSP里只输出结果。6.2 借书还书的核心业务判断链借阅功能是整个系统业务逻辑最密集的地方。先列出来一个借书动作需要做哪些判断该图书是否存在、当前库存是否大于0。该读者是否已经借了这本书且尚未归还防止重复借同一本书。一切正常则插入一条借阅记录status0due_time借出时间30天同时把books表里该书的current_stock减1。这三个判断分别在数据库层面和代码层面完成。库存是否大于0可以靠UPDATE books SET current_stock current_stock - 1 WHERE book_id ? AND current_stock 0这条SQL原子性地完成如果影响行数是1说明扣减成功是0说明库存不足。这是防止超借最常见的手段。还书的判断链更简单一点根据record_id查到对应借阅记录确认它确实是当前登录用户借的防止别人乱还。检查status是否为0借出中是才能还。更新借阅记录status改成1return_time设为当前时间。同时把books表里对应图书的current_stock加1。如果是逾期归还status可以改成2并在页面上给出提示。借阅和归还分别涉及两条数据变更插入借阅记录扣减库存 / 更新借阅记录增加库存如果要严谨需要放到一个数据库事务里执行保证要么全部成功要么全部失败。事务的写法是Connection conn null; try { conn JdbcUtils.getConnection(); conn.setAutoCommit(false); // 关闭自动提交 // 执行借阅记录的插入 // 执行库存扣减 conn.commit(); // 手动提交 } catch (Exception e) { if (conn ! null) conn.rollback(); // 回滚 e.printStackTrace(); } finally { if (conn ! null) conn.close(); }这里还牵扯到连接复用的问题事务期间的多个SQL操作必须使用同一个Connection对象不能每执行一条SQL就去连接池取一次新连接——那样就失去了事务的意义。DAO方法的设计上要么给DAO方法传入Connection参数要么把事务控制放在Service层统一管理。课程设计里最简单的做法是直接在借书方法内部用同一个Connection完成两步数据库操作把事务控制写在DAO层里。虽然不够分层优雅但胜在简单不易错。6.3 分页查询LIMIT offset, size和总页数计算图书数量稍微多一点列表页就需要分页。分页查询的实现套路很固定但几乎所有课程设计都在这里出过小问题。分页需要两个关键参数当前页码pageNum从1开始和每页条数pageSize。SQL语句长这样SELECT * FROM books ORDER BY book_id LIMIT ?, ?对应的DAO方法public ListBook findByPage(int pageNum, int pageSize) { String sql SELECT * FROM books ORDER BY book_id LIMIT ?, ?; int offset (pageNum - 1) * pageSize; // 执行查询offset作为第一个参数pageSize作为第二个参数 }同时还需要一个统计总记录数的方法public int count() { String sql SELECT COUNT(*) FROM books; // 返回总数量 }总页数的计算公式是totalPages (totalCount pageSize - 1) / pageSize这个公式保证即使总数不能被pageSize整除也能得到正确的页数。然后把pageNum、totalPages、当前页数据列表三个信息放进request域转发到JSP。JSP里翻页链接的地址带pageNum参数即可a href${pageContext.request.contextPath}/book/list?pageNum2下一页/a一个高阶一点的写法把分页信息封装成一个PageBean对象包含list、pageNum、pageSize、totalCount、totalPages字段。这样不仅图书列表能用以后借阅记录列表、用户列表都能复用。课程设计里如果你能在答辩时展示这个泛型PageBean属于明显的加分项。7. 项目部署到Tomcat与验收中的高频问题功能写完只是第一步项目能顺利部署运行、在演示过程中不出岔子才算完事。部署阶段的小问题能折腾人一整天我把最常见的几类集中梳理一下。7.1 部署的完整流程以IDEA为例部署JavaEE项目到Tomcat的标准路径项目结构选择Artifacts类型选Web Application Exploded表示以解压目录方式部署而不是打war包。在开发阶段用Exploded方式部署更快改完代码点Build就能热更新。Run/Debug Configurations里新增一个Tomcat Server - Local配置指定Tomcat安装目录和端口。Deployment选项卡里把项目Artifact添加到服务器。Application context访问路径建议设置为/library这样项目访问地址就是http://localhost:8080/library/。启动Tomcat控制台看到Starting ProtocolHandler表示启动成功浏览器访问即可。如果你是用VS Code开发的部署路径在前文4.2节已经讲过了直接打war包丢到webapps目录也可以。7.2 404、500、中文乱码的诊断思路部署过程中最常见的三个错误我挨个说一下定位思路。404错误通常是URL路径写错或者资源不存在。优先检查访问路径和项目的context path是否一致。比如context path是/library但访问地址写成了http://localhost:8080/login.jsp必然404。其次检查Servlet的WebServlet注解路径是否写对了是/login还是/login/末尾斜杠不能乱加。500错误通常是代码抛异常了。看Tomcat日志是最快的方式。Tomcat控制台或logs目录下的localhost.log里会有完整的异常堆栈。最常见的500原因是空指针查出来的user为null但直接调用了user.getUsername()、ClassNotFound驱动jar包没放到WEB-INF/lib下、数据库连接失败。顺着堆栈的前三行就能定位到具体代码行数。中文乱码问题乱码分为页面乱码和数据乱码。页面乱码基本是JSP没有声明UTF-8编码在JSP文件顶部加% page contentTypetext/html;charsetUTF-8 languagejava %就好。数据乱码要分清方向往数据库插入中文变乱码检查JDBC URL里的characterEncodingutf8参数是否齐全数据库表本身的字符集是不是utf8mb4。从数据库查出中文变乱码多半是连接参数缺了characterEncodingutf8或者JSP页面没设置UTF-8。页面展示时中文变乱码检查HTML的meta charsetUTF-8和JSP的page指令是否一致。乱码问题看起来头疼但80%的情况是URL加characterEncoding 页面声明UTF-8这两件事没做全。7.3 答辩时评委大概率会问的3个问题根据我旁听过的答辩现场经验评委对这个项目问得最多的问题集中在以下三个方向提前准备好回答思路问题一你项目里Servlet和JSP是怎么分工的回答思路Servlet负责处理请求和业务控制JSP负责数据展示。用户在页面上的每一次操作都会发一个HTTP请求到ServletServlet调用Java类访问数据库然后把结果通过request域传给JSPJSP用EL表达式和JSTL标签把数据显示出来。最后浏览器拿到的是经JSP渲染后的HTML页面。问题二数据库连接池是干什么用的假如不用它直接每次新建连接行不行回答思路数据库连接是一个重量级资源创建和关闭的开销很大。连接池在启动时就创建一定数量的连接放在池子里用的时候取用完归还。如果不用连接池每个请求都新建连接在并发量上来后会导致数据库连接数暴增性能急剧下降甚至数据库崩溃。回答这个问题的关键在于要提到资源复用这个词。问题三你这系统做了什么安全措施回答思路可以从三个层面回答——传输层面登录表单用POST提交密码不会出现在URL里存储层面数据库里存的是SHA-256加盐摘要而不是明文控制层面用Filter统一拦截未登录请求未登录只能访问登录页和资源文件。能把这三个层面说出来安全这部分就非常稳了。8. 一些代码结构上的建议最后聊一点代码组织上的心得这部分不是必须的但对最终分数和代码可维护性有实打实的好处。8.1 分层结构不要怕麻烦包名分好后面省事我见过很多课程设计代码所有类都堆在默认包下Servlet、DAO、实体类混在一起几百行一个类。建议你花十分钟规划一下包结构分类放好com.example.library ├── entity -- 实体类User、Book、BorrowRecord ├── dao -- 数据访问层UserDao、BookDao、BorrowRecordDao ├── servlet -- 控制层LoginServlet、BookServlet、BorrowServlet ├── filter -- 过滤器LoginFilter ├── util -- 工具类JdbcUtils、PasswordUtil └── service -- 业务层可选BorrowService包结构清晰了之后后续不管是自己维护还是找别人帮忙调bug都能快速定位到对应的代码位置。答辩时评委要看项目结构一眼过去就是加分印象。8.2 Service层要不要加看时间但加的好处明显上面分层结构里我给Service层标了可选。如果你的时间充裕我建议加上。Service层把DAO层的多个原子操作组合成完整的业务动作比如借书操作涉及查书、插记录、扣库存三步这样一个Service方法对应一个完整业务语义Servlet层会变得非常干净。加Service层还有一个隐性好处事务控制的代码可以集中在Service层处理比分散在DAO层或Servlet层都合理。时间不够的话不加也行但至少要保证Servlet层不要直接拼SQL把JDBC操作都收拢到DAO层。这种代码组织上的克制是JavaEE项目区别于一堆JSP页面直接连数据库的野路子项目的关键。8.3 JSP页面里的Java代码要克制JSP里不是不能写Java代码现在很多教材里还是% ... %的写法但课程设计评分通常对代码规范有要求。从成型项目的角度来说JSP里的Scriptlet越少越好。优先用EL表达式${book.bookName}和JSTL标签库c:forEach、c:if来展示数据。EL和JSTL的语法上手很快但注意一个前提使用JSTL需要导入两个标签库依赖jstl.jar 和 standard.jar并且每个用到的JSP页面顶部要加% taglib prefixc urihttp://java.sun.com/jsp/jstl/core %如果你用的Tomcat 9java.sun.com这个URI在部分高版本下可能会识别不了改成jakarta.tags.core后也可以。遇到JSTL标签不生效的情况优先查依赖jar包是否放到了WEB-INF/lib目录下以及taglib指令的URI是否和jar包里的版本匹配。写JSP页面时心里要有一条红线页面里能干的事只是取数据和循环展示除此之外的逻辑尽量都放到Servlet和Service层去。这样不管是对自己后面加功能还是答辩讲清楚设计思路都友好得多。评分标准里关于代码可读性分层合理性的分数靠的都是这种细节攒出来的。
返回列表