ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

JavaWeb医药管理系统大作业:登录鉴权、库存预警与事务处理实战

2026/10/5 3:41:41 拓冰建站 浏览量
JavaWeb医药管理系统大作业:登录鉴权、库存预警与事务处理实战 简介这是一份JavaWeb医药管理系统期末大作业项目面向计算机相关专业正在完成课程设计、期末大作业或毕业设计的学生也适合需要JavaWeb项目实战练习的学习者。项目经导师指导并通过评审获得98分源代码已在本机编译调试可导入运行。资源包共119个文件压缩后约3.01MB其中33个Java源文件与30个JSP页面构成核心业务和页面逻辑Class文件为编译产物SQL为数据库初始化脚本XML和JAR负责项目配置与依赖结构完整便于理解MVC分层。内容涵盖客户、供应商、医药、采购等管理功能可对照学习Servlet、DAO、JSP的调用流程与增删改查、数据持久化写法。目前已有94人学习使用下载后可直接查看源码中的业务逻辑与数据库表设计适合快速完成高分课设或作为二次开发基础。1. 医药管理系统JavaWeb大作业这是一个能让你期末拿高分的成熟选题期末大作业选题最怕什么怕题目太偏做不完怕题目太泛老师觉得你糊弄。医药管理系统恰好是JavaWeb方向的“标准答案”它覆盖登录鉴权、增删改查、库存预警、供应商管理这些老师必定会考的功能点同时药品过期、批次管理又自带业务亮点。更关键的是这个题目的源代码和数据库结构是透明的网上大量参考可以验证每个表的字段是否合理。无论你是想快速交出一份“高分项目”还是想借这个机会把JavaWeb完整跑通这套方案都值得你照着做一遍。2. 从选题到技术选型为什么医药管理系统最适合做JavaWeb期末大作业2.1 医药管理系统需求拆解你能用到的JavaWeb核心知识点医药管理系统从用户视角看只有三类角色管理员、药师、普通员工。管理员管员工账号和供应商药师管药品入库和出库普通员工只能查药和看库存。这个权限划分天然适合用Servlet的Filter做拦截再配合Session存储登录用户角色这就是JavaWeb里最高频的考点。从数据流上看药品从供应商采购进来进入药品表产生入库记录销售出去扣减库存产生出库记录。要保证库存不为负、要支持模糊查询、要能按供应商统计采购金额——这些功能正好把JDBC的CRUD、PreparedStatement参数绑定、ResultSet遍历这些基础操作全部串起来。更进阶一点你还可以加一个药品过期提醒写一条查询“当前日期距有效期小于30天的记录”这条SQL写完老师就知道你不是只会照抄增删改查。除了功能这个题目还能展示你的工程化意识。角色权限表、药品表、供应商表、出入库记录表之间是典型的一对多和多对一关系外键怎么设计、字段要不要加索引、库存扣减要不要用事务这些都是期末答辩时老师爱问的点。哪怕你只做到“能增删改查”只要把这些知识点在文档里写清楚分数也不会低。2.2 技术栈选型JSP/Servlet还是Spring Boot期末大作业的务实选择常见做法是JSP Servlet MySQL Tomcat这是最稳妥的组合也是大多数JavaWeb教材的默认配置。JSP负责显示Servlet负责接收请求和跳转DAO层用JDBC连接数据库三层结构一目了然。老师打开你的项目时能一眼找到每个请求对应的Servlet和JSP评分也容易给依据。用Spring Boot当然更“现代”但期末大作业是打分制不是面试。Spring Boot自带内嵌Tomcat起步依赖多一旦版本和JDK对不上启动直接报错你还得花半小时排查依赖冲突。而且不少老师会抽查学生对代码的理解让你讲一个从页面到数据库的完整流程。你用Spring Boot写个RestController看起来高大上但讲不清自动配置原理反而会被追问到翻车。我的建议是如果课程本身就是SSH或SSM那就跟着课程走如果课程是纯JSP/Servlet就老老实实用JSPServlet。技术栈越简单越容易把精力花在业务功能上。数据库用MySQL 5.7或8.0都可以连接池用C3P0或Druid都行但别同时引多个数据库连接池那是自己给自己挖坑。3. 搭起项目骨架IDEA里运行JavaWeb项目的最小配置与数据库设计3.1 用IDEA创建一个可运行的JavaWeb项目Maven坐标与目录结构在IDEA里新建一个Maven项目选择maven-archetype-webapp骨架这一步能少配很多文件。如果你用社区版IDEA没有直接创建JavaWeb工程的入口也可以先建一个普通Maven工程然后手动补src/main/java、src/main/resources、src/main/webapp三个目录。关键是把pom.xml里的打包方式设为war并引入Servlet和JSP依赖。下面是一个最简可用的pom.xml核心依赖我把版本号写成了变量方便你统一调整project modelVersion4.0.0/modelVersion groupIdcom.hospital/groupId artifactIddrug-manage/artifactId version1.0.0/version packagingwar/packaging properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties dependencies !-- Servlet APITomcat 9对应javax.servlet-api 4.0.1 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL标签库用于页面遍历 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL驱动8.0对应8.0.33 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies /project这里的scope是provided意思是编译时用到但运行时由Tomcat提供避免和你本地Tomcat里的Servlet API冲突。如果你漏配这一步启动时可能遇到java.lang.LinkageError或者NoSuchMethodError。随后在IDEA的Run/Debug Configuration里添加Tomcat Server注意Artifact选war exploded模式。这个模式会把项目解压目录直接作为部署目录改JSP不用重启Tomcat对调试体验提升很大。运行前还要在Deployment面板里把Application context设成/这样访问路径就是http://localhost:8080/而不是/drug-manage_war_exploded/。完整的目录结构大概是这样src/main/java com.hospital.dao // JDBC访问层 com.hospital.entity // 实体类 com.hospital.service // 业务逻辑层 com.hospital.servlet // 控制器层 com.hospital.filter // 过滤器 com.hospital.util // 数据库连接工具类 src/main/resources c3p0-config.xml // 数据库连接池配置 src/main/webapp WEB-INF web.xml jsp login.jsp drugList.jsp ...这个结构对应的是最传统的三层架构。实体类只放字段和getter/setterDAO只写SQL访问Servlet只做请求转发和参数接收。千万别把SQL写在JSP里面那种代码虽然能跑但答辩时被问“为什么这样写”你很难自圆其说。3.2 数据库设计医药管理系统需要几张表各字段怎么定数据库是这个项目的评分重点表设计得规范老师说废话不多说直接给分。最精简的版本需要四张表用户表、药品表、供应商表、出入库记录表。如果还想做员工管理可以再拆一个员工表但期末大作业四张表足够。下面是一份经过调整的建表SQL已去掉了与主题无关的字段CREATE DATABASE drug_db DEFAULT CHARACTER SET utf8mb4; USE drug_db; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(20) NOT NULL, role TINYINT NOT NULL DEFAULT 2 COMMENT 1-管理员 2-药师 3-员工, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE supplier ( id INT PRIMARY KEY AUTO_INCREMENT, supplier_name VARCHAR(50) NOT NULL, contact_person VARCHAR(20), phone VARCHAR(20), address VARCHAR(100), UNIQUE KEY uk_supplier_name (supplier_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE drug ( id INT PRIMARY KEY AUTO_INCREMENT, drug_name VARCHAR(50) NOT NULL, specification VARCHAR(50) COMMENT 规格如 0.25g*24片, dosage_form VARCHAR(20) COMMENT 剂型, supplier_id INT NOT NULL, purchase_price DECIMAL(10,2) NOT NULL, sale_price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, warn_line INT NOT NULL DEFAULT 50 COMMENT 库存预警线, expiry_date DATE NOT NULL COMMENT 有效期, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_drug_supplier FOREIGN KEY (supplier_id) REFERENCES supplier(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE stock_record ( id INT PRIMARY KEY AUTO_INCREMENT, drug_id INT NOT NULL, change_type TINYINT NOT NULL COMMENT 1-入库 2-出库, quantity INT NOT NULL, operator_id INT NOT NULL, record_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_record_drug FOREIGN KEY (drug_id) REFERENCES drug(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个容易踩坑的地方user表是MySQL保留字用反引号包起来对象名否则导入SQL时直接报You have an error in your SQL syntax。药品的单价为什么用DECIMAL(10,2)而不是FLOAT因为FLOAT是浮点近似值金额计算会丢掉精度期末作业如果被老师查到价格对不上很尴尬。expiry_date用DATE类型不要用字符串存储否则后面做“到期提醒”只靠字符串比较很容易出错。所有表的字符集统一用utf8mb4不然插入“药品名称”里的生僻字时可能变成问号。如果你的MySQL版本是5.6或更旧可能不支持CURRENT_TIMESTAMP作为DATETIME默认值这时可以在建表后手动把默认值改成NOW()或者在插入语句里显式写入当前时间。4. 核心功能落地登录鉴权、药品增删改查与库存预警的代码实现4.1 登录与权限控制用Filter拦下未登录请求登录模块是每个大作业都要求的功能但很多同学只做了登录页面没做权限拦截。老师一访问你的项目首页却发现可以直接绕过登录进入药品列表这基本等于告诉老师“我不懂Session”。正确的做法是写一个LoginFilter在web.xml里配置过滤所有/jsp/*路径只放行login.jsp和登录校验的Servlet。下面这个Filter按功能做了简化只保留了核心判断package com.hospital.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; WebFilter(/jsp/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 放行登录页本身和登录的校验接口 String uri request.getRequestURI(); if (uri.contains(login.jsp) || uri.contains(/login)) { chain.doFilter(req, resp); return; } HttpSession session request.getSession(false); if (session null || session.getAttribute(currentUser) null) { response.sendRedirect(request.getContextPath() /jsp/login.jsp); return; } chain.doFilter(req, resp); } Override public void init(FilterConfig filterConfig) throws ServletException { } Override public void destroy() { } }逻辑很简单先从请求里拿到HttpSession注意getSession(false)不要传true否则每次请求都会自动创建一个新Session导致判断永远不生效。如果Session里没有currentUser这个属性就重定向到登录页。request.getContextPath()能取到当前应用上下文路径避免直接写死/drug-manage这样项目换个名字部署也不会失效。有一点容易被忽略如果你用了WebFilter(/jsp/*)注解又同时写了web.xml可能出现过滤器注册两次的情况。建议只用一种方式我习惯在web.xml里管理过滤器因为那里还能同时控制参数初始化和过滤顺序。4.2 药品管理模块从DAO到Servlet到JSP的完整链路药品管理是整个项目的核心。这里我把DAO层的代码简化成一个DrugDao类里面包含分页查询、按名称模糊查询、新增、修改、删除五个操作。逻辑不复杂但要注意每个方法都单独获取连接释放资源时用finally块避免连接泄漏。先看查询部分包含一个分页参数package com.hospital.dao; import com.hospital.entity.Drug; import com.hospital.util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class DrugDao { public ListDrug findByName(String keyword, int page, int pageSize) { ListDrug list new ArrayList(); String sql SELECT d.*, s.supplier_name FROM drug d LEFT JOIN supplier s ON d.supplier_id s.id WHERE d.drug_name LIKE ? LIMIT ?, ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setInt(2, (page - 1) * pageSize); ps.setInt(3, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } } catch (SQLException e) { e.printStackTrace(); } return list; } public int countByName(String keyword) { String sql SELECT COUNT(*) FROM drug WHERE drug_name LIKE ?; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { return rs.getInt(1); } } } catch (SQLException e) { e.printStackTrace(); } return 0; } private Drug mapRow(ResultSet rs) throws SQLException { Drug d new Drug(); d.setId(rs.getInt(id)); d.setDrugName(rs.getString(drug_name)); d.setSpecification(rs.getString(specification)); d.setStock(rs.getInt(stock)); d.setWarnLine(rs.getInt(warn_line)); d.setExpiryDate(rs.getDate(expiry_date)); d.setSupplierName(rs.getString(supplier_name)); return d; } }mapRow里的getDate返回的是java.sql.Date实体类里接受ExpiryDate最好也定义为java.util.Date否则前端格式化会很别扭。try-with-resources是Java 7后的语法能自动关闭Connection和PreparedStatement比手动finally关连接少写好几行。下一步写ServletDrugServlet只负责接收请求参数、调用DAO、把结果放到request的attribute里再转发给JSP。一个Servlet里通常会有很多if判断操作类型比如actionlist、actionadd、actionupdate、actiondelete。下面只展示列表和新增两个分支的骨架package com.hospital.servlet; import com.hospital.dao.DrugDao; import com.hospital.entity.Drug; import com.hospital.service.DrugService; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.math.BigDecimal; import java.util.List; WebServlet(/drug) public class DrugServlet extends HttpServlet { private DrugDao drugDao new DrugDao(); private DrugService drugService new DrugService(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (list.equals(action)) { String keyword req.getParameter(keyword); if (keyword null) { keyword ; } int page 1; String pageStr req.getParameter(page); if (pageStr ! null !pageStr.isEmpty()) { page Integer.parseInt(pageStr); } int pageSize 10; ListDrug list drugDao.findByName(keyword, page, pageSize); int total drugDao.countByName(keyword); req.setAttribute(list, list); req.setAttribute(total, total); req.setAttribute(page, page); req.setAttribute(pageSize, pageSize); req.getRequestDispatcher(/jsp/drugList.jsp).forward(req, resp); } } Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String action req.getParameter(action); if (add.equals(action)) { String drugName req.getParameter(drugName); String specification req.getParameter(specification); String supplierId req.getParameter(supplierId); BigDecimal salePrice new BigDecimal(req.getParameter(salePrice)); int quantity Integer.parseInt(req.getParameter(stock)); // 调用Service层执行入库同时写stock_record表 boolean ok drugService.addDrug(drugName, specification, Integer.parseInt(supplierId), salePrice, quantity); resp.sendRedirect(req.getContextPath() /drug?actionlist); } } }doPost里第一行req.setCharacterEncoding(UTF-8)一定不能漏但要注意必须在读取任何参数之前调用。如果你用了WebServlet注解并且同时定义了多个Servlet类还要检查每个Servlet的urlPatterns不能互相覆盖否则IDEA启动时会报Servlet path/drug conflicts with existing mapping。JSP页面这里不展开但核心是使用EL表达式和JSTL标签比如在drugList.jsp里这样遍历c:forEach items${list} vard tr td${d.id}/td td${d.drugName}/td td${d.stock}/td td c:if test${d.stock d.warnLine} span stylecolor:red库存不足/span /c:if /td /tr /c:forEach这里${list}就是Servlet里setAttribute进去的List。如果你在JSP里直接写% %拼HTML也能跑但老师如果要求用EL表达式你就得重写了。4.3 库存预警与过期提醒SQL查询怎么写得让老师眼前一亮库存预警业务逻辑不复杂但很多同学只会写if (stock 100)这种硬编码。更好的做法是把预警线做成药品表里的warn_line字段这样每个药可以单独设置。查询时只需要把低于预警线的记录找出来SELECT id, drug_name, stock, warn_line FROM drug WHERE stock warn_line ORDER BY stock ASC;这条SQL本身很简单老师关心的是你会不会处理它。你可以把预警列表放在后台首页而不是塞到药品列表里。用一个独立Servlet查询每次管理员登录后加载一次页面顶部就显示“当前有3种药品低于库存预警线”点击跳转到详细列表。这样的交互设计只要做到位项目就能脱离“课程设计平均水平”。过期提醒稍微复杂一点要同时考虑药品的expiry_date和当前时间还要区分“即将过期”和“已过期”SELECT id, drug_name, expiry_date, DATEDIFF(expiry_date, CURDATE()) AS days_left FROM drug WHERE expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY) OR expiry_date CURDATE() ORDER BY expiry_date ASC;DATEDIFF能算出剩余天数这个字段可以直接显示在页面上再用c:if判断小于0就标红。这里有个细节CURDATE()是数据库服务器的时间如果你的电脑时区不对查询结果可能和你页面看到的时间差一天。实际交付时建议用Java程序里new Date()传给SQL参数而不是依赖数据库时间。如果你的项目还要做统计报表可以再加一条供应商采购金额汇总查询SELECT s.supplier_name, IFNULL(SUM(d.purchase_price * r.quantity), 0) AS total_purchase FROM supplier s LEFT JOIN drug d ON s.id d.supplier_id LEFT JOIN stock_record r ON d.id r.drug_id AND r.change_type 1 GROUP BY s.id;这条SQL用到了LEFT JOIN、IFNULL和GROUP BY属于中等难度但老师非常认的写法。它反映的不是“学会了JSP”而是“能拿数据库熟练查业务”。5. 医药管理系统项目避坑数据库连接、中文乱码与IDEA部署的常见问题5.1 连接池连不上MySQL 8驱动类与时区问题现象项目在别人电脑上跑得好好的到你那里启动后任何查询都报java.sql.SQLException: Access denied for user rootlocalhost或者Communications link failure。原因你用的mysql-connector-java还是5.x版本但MySQL是8.0。5.x驱动连接8.0数据库会因加密方式不兼容被拒绝另外MySQL 8的驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.DriverURL里还必须带上serverTimezone参数。解决把pom里的驱动版本改成8.0.x并在数据库连接URL末尾加上?useSSLfalseserverTimezoneAsia/Shanghai。如果还是报错检查你MySQL里root账号的plugin是不是caching_sha2_password是的话可以考虑新建一个专用账号用mysql_native_password插件避免本地权限玄学。5.2 JSP页面中文乱码三个位置要统一编码现象页面上的中文全是“”或者从数据库读出来的中文变成乱码但数据库往表里插数据时又是好的。原因编码不一致。数据库连接URL没有指定characterEncodingJSP文件本身保存成了GBKServlet读参数前没有setCharacterEncoding这三个位置任何一个不统一结果都是乱码。解决三个地方全部设成UTF-8。数据库URL加characterEncodingutf8IDEA右下角把文件编码改成UTF-8JSP头部写死% page contentTypetext/html;charsetUTF-8 languagejava %Servlet的doPost第一行加req.setCharacterEncoding(UTF-8)。改完之后清一遍Tomcat缓存重启再试。5.3 Tomcat部署后找不到类Artifacts的坑现象IDEA启动Tomcat不报错但访问任何Servlet都返回HTTP 404或者报java.lang.ClassNotFoundException: com.hospital.servlet.LoginServlet。原因你把项目部署到Tomcat时IDEA的Artifact没有包含src/main/java目录下编译出的class文件。常见于手工创建WEB-INF目录时漏掉了classes目录IDEA没有自动同步编译输出。解决打开File - Project Structure - Artifacts在WEB-INF/classes上右键选择Put into Output Root让编译输出目录指向这里。更简单的方法是删掉旧Artifact重新按Web Application Exploded创建一次IDEA会自动把编译好的classes和resources目录打包进去。这里没有后悔药改完必须重新Build。5.4 密码明文存储被老师挑刺加盐MD5的快速改造现象数据库里user表的password字段直接存的是123456老师浏览数据库时看到了任何人的密码当场问你怎么防止密码泄露。原因你图省事把登录校验写成了password 123456这种纯字符串比较。虽然是期末大作业但密码明文存储属于最基础的安全常识被点名很亏。解决密码在注册时用MD5加盐后存储登录时把用户输入的密码加同样盐再计算MD5去和库里的值比。代码很短在UserDao里加一个方法即可public String md5WithSalt(String input, String salt) { String base salt input; StringBuilder sb new StringBuilder(); try { MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(base.getBytes(UTF-8)); for (byte b : bytes) { sb.append(String.format(%02x, b)); } } catch (Exception e) { e.printStackTrace(); } return sb.toString(); }这里盐可以用“用户名”本身或者单独加一个固定字符串。如果想更专业就用BCrypt但需要额外引依赖期末大作业用MD5加盐足够说明你考虑到了安全问题。答辩时老师问为什么不用明文你可以说“密码经过不可逆摘要存储即使数据库泄露也无法直接拿到明文”。6. 让项目从“能用”到“高分”的加分项事务处理、日志与答辩准备6.1 给药品入库/出库加事务C3P0连接池的连接复用方案很多同学的入库操作是只更新drug表的stock不写stock_record。这导致库存变了但看不到历史记录老师随便问一句“这批药是什么时候进的”你就卡壳。正确的入库逻辑是“更新库存 插入一条入库记录”两步一起做这两步要么同时成功要么同时失败。用JDBC处理事务可在DrugService里手动控制Connection下面是一个最小可用的模板package com.hospital.service; import com.hospital.dao.DrugDao; import com.hospital.util.DBUtil; import java.sql.Connection; public class DrugService { private DrugDao drugDao new DrugDao(); public boolean addDrug(String name, String spec, int supplierId, java.math.BigDecimal price, int quantity) { try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 1. 向drug表插入或更新库存 drugDao.insertDrug(conn, name, spec, supplierId, price, quantity); // 2. 向stock_record表插入入库记录 drugDao.insertStockRecord(conn, name, 1, quantity); conn.commit(); return true; } catch (Exception e) { e.printStackTrace(); // 这里为了简化没有rollback语句但你必须知道异常时要回滚 return false; } } }关键点有三个setAutoCommit(false)之后commit()之前的所有SQL都处于同一个事务中DAO方法必须接收外部传入的Connection不能再自己调DBUtil.getConnection()否则事务被拆成两个连接根本控制不了捕获异常后应该调用conn.rollback()上面代码因为用了try-with-resourcesConnection关闭时会自动回滚但显式写出来更好。如果你的连接池是C3P0DBUtil.getConnection()每次取到的都是池里的同一个物理连接所以在同一个线程里调用两次getConnection()拿到的大概率是同一个对象。但千万不要依赖这个“大概率”正确做法永远是传递Connection参数。6.2 答辩时老师常问的三个问题与回答口径老师看过太多大作业能现场把你问懵的一般是这三个题。提前准备口径不比多写一个功能更省事第一问“登录校验为什么不写在JSP里而要用Filter”你可以说Filter是Servlet规范自带的组件能在请求进入Servlet之前统一拦截避免了每个JSP页面重复判断Session的代码。同时增加新的JSP页面时只要路径在/jsp/*下就会自动被保护不会漏掉某个人没写校验。第二问“药品库存为什么用DECIMAL而不是FLOAT”你可以说FLOAT是近似值多次累计入库出库后可能出现0.01元级别的误差医药系统涉及价格结算必须用定点数DECIMAL保证精度。这个回答同时展示了你对基本数据类型和业务场景的思考。第三问“数据库连接不关闭会怎样”你可以说每个数据库连接都是稀缺资源不关闭会让连接池耗尽系统达到一定访问量后直接卡死。我这里用try-with-resources自动关闭相当于上了保险。这三问有个共同点是问得很低级但回答里如果能带出“规范”“资源管理”“边界处理”这些词老师会认为你不仅仅是在“写页面”而是在“做系统”。6.3 再给一个额外的加分技巧登录日志如果时间允许再给项目加一个登录日志功能。你可以建一张login_log表记录登录用户名、IP、登录时间。在登录成功的分支里插入一行记录然后在管理员首页展示最近10条日志。这个功能工作量不大但它在答辩时能让老师看到你对“可维护性”和“审计”这两个词的敏感度比多做一个花里胡哨的图表有用得多。我自己的经验是期末大作业最怕的不是功能少而是功能多但每个都半成品。与其堆五个模块不如把登录、药品增删改查、库存预警、事务记录这四个做完整。答辩前我一定会在自己电脑上从头跑一遍项目清空数据库再初始化确认没有“在我电脑上是好的”这回事。数据库脚本也一定要用source命令从纯命令行导入一遍避免Navicat或dbx这类工具帮你处理了隐含问题结果换台电脑全盘崩。希望这些踩坑记录能帮你少走一段弯路把时间花在真正加分的地方上。本文还有配套的精品资源点击获取