ARTICLE DETAIL

资讯详情

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

JavaWeb药品存销信息管理系统课设:数据库设计与事务控制完整指南

JavaWeb药品存销信息管理系统课设:数据库设计与事务控制完整指南 简介一套面向软件工程课程大作业的基于JavaWeb的药品存销信息管理系统源代码与数据库文件采用经典分层架构设计适合高校学生用于课程设计、毕业设计或JavaWeb开发入门实践。压缩包内共623个文件整体大小46.67MB核心包括108个Java源文件、36个JSP页面、69个Jar依赖库、65个XML配置、30个JS与22个CSS前端样式以及1份SQL数据库脚本同时提供编译后的class文件方便直接部署运行。目前已有99人浏览学习作为软件工程综合实训项目具有一定的参考价值。系统覆盖药品信息管理、入库、出库、库存查询与统计报表等核心业务模块可帮助学习者理解JavaWeb项目从数据库设计到前后端交互的完整实现流程。源码结构清晰、注释完整既可作为项目答辩的演示素材也能为后续扩展开发提供基础。1. 药品存销信息管理系统JavaWeb 课设题目里最扎实的一条路线同样是软件工程课程设计有人交上去的药品存销系统拿了优秀有人代码能跑通却只拿及格差距往往不在功能数量而在数据库设计和事务处理。药品存销信息管理系统是 JavaWeb 课设里非常经典的一类选题它既有药品档案、供应商、客户这些基础数据又有入库、出库、库存变更这条业务主线恰好把数据库建表、JDBC 增删改查、Servlet 控制、JSP 页面全部串在一起。下面这套方案的思路适合两类人一是课程设计选了存销方向但不知从哪落手的同学二是代码能跑但答辩总被问住、想系统补齐设计依据的人。照着建库、写代码、排错最后也能把每个关键选择讲清楚。2. 运行环境与技术选型把 JDK、Tomcat、MySQL、IDEA 四件套一次配齐2.1 为什么不用 Spring Boot课设考察的边界要看清先回答一个绕不开的问题同样是做管理系统为什么不直接上 Spring BootJavaWeb 课程的评分点通常落在 Servlet 生命周期、请求响应处理、JDBC 和手写 SQL 上这些正是 Spring Boot 帮你藏起来的部分。答辩时老师问一句“你这条链路里 Servlet 在哪”如果答不上来功能再多也拿不到高分。标题明确写了“基于 JavaWeb”用 Servlet JSP JDBC 的经典组合最贴合题意也最不容易在题目审核时翻车。什么时候可以用 Spring Boot如果你的老师不限制技术栈或者你已经能熟练讲清楚 MVC 和 ORM那用 Spring Boot 确实省事。但从零起步做课设Servlet 路线投入产出比更高因为 request、response、forward、redirect 这些概念在后续学 SpringMVC 时全部通用学一遍不亏。另一个现实问题是不少学校机房和老师电脑上的 Tomcat 还是 8.5 或 9.0Spring Boot 内置容器版本更高反而容易出现环境兼容性纠纷。所以这套方案定下来JDK 8 或 11Tomcat 8.5 或 9.0MySQL 5.7 或 8.0IDE 用新版 IDEA 的 Ultimate 版本。社区版也能做但配置 Web 项目时要手动装插件和设置 Application Server麻烦不少。Tomcat 10 之后包名从 javax.servlet 改成了 jakarta.servlet很多教材和现成代码片段直接编译不过课设阶段没必要给自己挖这个坑。2.2 本地环境配置版本匹配与三个必配项环境问题占了课设踩坑的一半以上。先把版本对应关系确认好再动手建项目能省掉后面大量玄学报错。我一般建议直接用 Maven 建 war 项目因为依赖 jar 会自动下载交作业时把 pom.xml 一起交老师导入项目就能识别不会出现“打开你项目少一个 mysql-connector 包”的尴尬。java -version mvn -v mysql --version echo $CATALINA_HOME这四条命令依次确认 JDK、Maven、MySQL、Tomcat 是否在 PATH 里。第一行如果显示的是 1.8 或 11都和 Tomcat 8.5/9.0 兼容如果显示 17 或更高建议把 Tomcat 换到 9.0 以上。mvn -v没输出也不影响改用 IDEA 自带的 Maven 或者直接建普通 Java 项目再加 Web 支持即可。$CATALINA_HOME在 IDEA 内嵌 Tomcat 模式下往往是空的这很正常不用纠结。组件推荐版本需要注意的点JDK1.8 或 11版本过高时需配套 Tomcat 9Tomcat8.5 / 9.0包名仍是 javax.servlet与教材一致MySQL5.7 / 8.08.0 的驱动类名和认证方式有变化mysql-connector-java5.1.49 或 8.0.x8.0 必须配 serverTimezone 参数IDEA2023 及以上Ultimate 版配置 Web 项目更顺手新版 IDEA 创建 JavaWeb 项目时要注意 New Project 里的模板会默认生成 Jakarta EE 结构这和 Tomcat 8.5 的 javax 命名空间对不上。常见的稳妥做法是先建一个普通的 Maven Java 项目右键项目选择 Add Framework Support勾选 Web Application再在 Project Structure 里配置 Artifacts把依赖 jar 放进 WEB-INF/lib。这三处配置是 IDEA 运行 JavaWeb 项目配置的关键漏掉任何一处部署时不是 404 就是 ClassNotFoundException。2.3 三层架构目录源代码先按职责拆开项目源码的组织方式直接影响答辩印象分。把 Controller、业务逻辑、数据库操作全写在一个 Servlet 里当然能跑但老师翻代码时看到几百行堆在一起第一印象就差了。我习惯按下面的目录结构来建包src/main/java/com/example/pharmacy ├── entity # 实体类Drug, Supplier, Customer, Stock, Purchase, Sale ├── dao # 数据访问层写 SQL操作数据库 ├── service # 业务逻辑层事务控制、库存校验 ├── servlet # 控制层接收请求调 service转发到 JSP ├── filter # 过滤器编码处理、登录拦截 └── util # 工具类DBUtil、MD5Util、PageUtil src/main/webapp ├── WEB-INF/jsp # JSP 页面放在 WEB-INF 下只能转发访问 ├── css # 静态样式 └── js # 前端脚本这个结构不是 Maven 强制要求的但按它拆完之后每个类的职责非常清晰dao 里只出现 SQL 和 PreparedStatement 参数绑定service 里只出现业务规则和事务提交回滚servlet 里只出现参数接收和页面跳转。答辩的时候说一句“各层职责单一”比讲十个页面效果都好。还有一个细节值得注意JSP 页面放进 WEB-INF 目录后用户不能直接在浏览器地址栏输入路径访问只能通过 Servlet 的 forward 跳转进入。这样做一方面是安全考虑防止未登录用户绕过拦截器直接打开页面另一方面也是一个很自然的答辩话题点足以讲清楚 forward 和 sendRedirect 的区别。3. 数据库建模与建库脚本七张表把药品“存”和“销”串起来3.1 核心表清单与字段设计从业务动作倒推表结构药品存销系统的业务动作其实就四个维护药品档案、供应商供货入库、向客户销售出库、查看当前库存。围绕这四个动作我设计了七张表表名作用关键字段admin登录账号username, passworddrug药品档案drug_code, drug_name, specification, approval_numbersupplier供应商supplier_name, contact, phonecustomer客户customer_name, contact, phonestock库存表drug_id, quantity, warn_linepurchase入库单supplier_id, drug_id, quantity, purchase_pricesale销售出库单customer_id, drug_id, quantity, sale_price这里有个刻意的设计药品档案和库存分成了两张表。原因是两者的变更频率完全不同drug 表只有在药品信息调整时才更新而 stock 表每次入库出库都要动。如果合在一张表里更新库存时要连带锁定药品信息逻辑上和数据库层面都更别扭。分表之后drug 是静态基础数据stock 是动态状态数据各管各的也符合第三范式的要求。实体关系也比较直观drug 和 stock 是一对一purchase/sale 分别通过 supplier_id、customer_id 关联供应商和客户通过 drug_id 关联药品。入库单和销售出库单没有拆成“主表加明细表”因为课设场景下每次出入库只涉及一种药品拆成两级反而增加页面和代码的复杂度。如果老师问为什么不做成多药品单据可以用“本次业务一个单据对应一个药品保持模型简单”来回答这不是缺点是需求边界。3.2 建库建表 SQL一张表对应一个业务动作下面这份 SQL 是整套系统的地基。建库时直接指定 utf8mb4避免后面出现中文乱码和 emoji 字符存不进去的问题。每个表都用 InnoDB 引擎因为事务控制和外键约束都依赖它。CREATE DATABASE IF NOT EXISTS pharmacy DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE pharmacy; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT 存储MD5值, real_name VARCHAR(50) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE drug ( id INT PRIMARY KEY AUTO_INCREMENT, drug_code VARCHAR(50) NOT NULL UNIQUE, drug_name VARCHAR(100) NOT NULL, specification VARCHAR(100) COMMENT 规格0.25g*24粒, dosage_form VARCHAR(30) COMMENT 剂型胶囊/片剂/注射液, manufacturer VARCHAR(100) COMMENT 生产厂家, approval_number VARCHAR(50) COMMENT 批准文号, purchase_price DECIMAL(10,2) COMMENT 进货价, sale_price DECIMAL(10,2) COMMENT 销售价, shelf_life INT COMMENT 有效期天数, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE supplier ( id INT PRIMARY KEY AUTO_INCREMENT, supplier_name VARCHAR(100) NOT NULL, contact VARCHAR(50), phone VARCHAR(20), address VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE customer ( id INT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(100) NOT NULL, contact VARCHAR(50), phone VARCHAR(20), address VARCHAR(200) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE stock ( drug_id INT PRIMARY KEY, quantity INT NOT NULL DEFAULT 0, warn_line INT NOT NULL DEFAULT 50 COMMENT 库存预警线, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, CONSTRAINT fk_stock_drug FOREIGN KEY (drug_id) REFERENCES drug(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE purchase ( id INT PRIMARY KEY AUTO_INCREMENT, supplier_id INT NOT NULL, drug_id INT NOT NULL, quantity INT NOT NULL, purchase_price DECIMAL(10,2) NOT NULL, total_amount DECIMAL(10,2) NOT NULL, operator_id INT, purchase_time DATETIME DEFAULT CURRENT_TIMESTAMP, remark VARCHAR(200), CONSTRAINT fk_purchase_supplier FOREIGN KEY (supplier_id) REFERENCES supplier(id), CONSTRAINT fk_purchase_drug FOREIGN KEY (drug_id) REFERENCES drug(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE sale ( id INT PRIMARY KEY AUTO_INCREMENT, customer_id INT NOT NULL, drug_id INT NOT NULL, quantity INT NOT NULL, sale_price DECIMAL(10,2) NOT NULL, total_amount DECIMAL(10,2) NOT NULL, operator_id INT, sale_time DATETIME DEFAULT CURRENT_TIMESTAMP, remark VARCHAR(200), CONSTRAINT fk_sale_customer FOREIGN KEY (customer_id) REFERENCES customer(id), CONSTRAINT fk_sale_drug FOREIGN KEY (drug_id) REFERENCES drug(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个字段类型要特别说明。金额一律用 DECIMAL(10,2)不要用 DOUBLE因为浮点数在累加和比较时会产生精度误差答辩时讲这个点很加分。药品有效期用 INT 保存天数比如 730 代表两年计算过期提醒时拿当前日期加天数做比较比直接操作字符串日期更可靠。入库时间、出库时间都设置 DEFAULT CURRENT_TIMESTAMP这样 Java 代码里少传一个字段还不会因为时间格式解析出错。外键约束在课设里建议保留因为老师检查 ER 图时会看参照完整性但在说明里要能讲出另一层理解真实生产环境为了性能和大数据量下灵活删除常常只保留逻辑外键不建物理约束。这个说法能让老师知道你不只是照着教材抄。3.3 初始数据与字段细节让页面打开就有东西建完表之后必须插入演示数据否则页面打开一片空白根本看不出系统效果。初始数据不用多够演示即可一个管理员账号、两个供应商、两个客户、三到五个药品以及对应的库存记录。USE pharmacy; INSERT INTO admin (username, password, real_name) VALUES (admin, 0192023a7bbd73250516f069df18b500, 系统管理员); -- 0192023a7bbd73250516f069df18b500 是 admin123 的 MD5 INSERT INTO supplier (supplier_name, contact, phone, address) VALUES (示例医药批发公司, 张经理, 13800000001, 上海市浦东新区), (康健药业有限公司, 李经理, 13800000002, 北京市朝阳区); INSERT INTO customer (customer_name, contact, phone, address) VALUES (仁心大药房, 王店长, 13900000001, 杭州市西湖区), (安康医院药房, 赵主任, 13900000002, 南京市鼓楼区); INSERT INTO drug (drug_code, drug_name, specification, dosage_form, manufacturer, approval_number, purchase_price, sale_price, shelf_life) VALUES (YPGJ0001, 阿莫西林胶囊, 0.25g*24粒, 胶囊剂, 示例制药有限公司, 国药准字H99990001, 8.50, 15.00, 730), (YPGJ0002, 布洛芬缓释片, 0.3g*20片, 片剂, 示例药业股份有限公司, 国药准字H99990002, 12.00, 22.50, 1095), (YPGJ0003, 维生素C片, 100mg*100片, 片剂, 示例健康药业, 国药准字H99990003, 3.20, 6.00, 730); INSERT INTO stock (drug_id, quantity, warn_line) VALUES (1, 800, 100), (2, 450, 100), (3, 180, 50);注意 admin 表里存的是 MD5 后的密文不是明文密码。演示期间这样处理已经足够答辩时还可以顺势提一句加盐方案。药品表里的批准文号是演示数据格式符合国药准字的样式但并不是真实存在的号码正式交作业建议换用自己按规则编造的一批测试数据避免争议。数据量方面每张表两三行就够完成功能演示。如果需要模拟大批量数据可以在答辩前一天用存储过程循环插入几千条比如给 stock 表生成 20 个药品的不同库存量这样列表页面有分页效果也能展示查询效率——但存储过程脚本不属于课程设计核心不要放太多篇幅。4. 源代码的分层实现从 JDBC 工具类到事务控制的完整代码4.1 JDBC 工具类与连接管理把数据库配置从代码里抽走一个项目里所有 DAO 都要获取数据库连接如果每个类都写一遍 Class.forName 和 DriverManager.getConnection代码冗余不说改密码时还要全局替换。我习惯写一个 DBUtil 类统一管连接把数据库连接参数放到 db.properties 配置文件中。# src/main/resources/db.properties drivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/pharmacy?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 usernameroot password123456package com.example.pharmacy.util; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class DBUtil { private static String url; private static String username; private static String password; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { Properties props new Properties(); props.load(in); Class.forName(props.getProperty(driver)); url props.getProperty(url); username props.getProperty(username); password props.getProperty(password); } catch (Exception e) { throw new ExceptionInInitializerError(数据库配置初始化失败: e.getMessage()); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }这段代码里最关键的参数是 url 中那一长串后缀。useSSLfalse是关闭 MySQL 8 默认的 SSL 警告serverTimezoneAsia/Shanghai解决 MySQL 8 与 JDBC 时区差异导致的报错characterEncodingutf8保证中文正常传输。MySQL 5.7 用户把 driver 换成com.mysql.jdbc.Driver也能跑但建议直接统一用com.mysql.cj.jdbc.Driver兼容性更好。classpath 中找不到 db.properties 时getResourceAsStream返回 nullprops.load(in)会抛空指针所以初始化块里的异常提示一定要写清楚。4.2 BaseServlet 封装一个反射把 Servlet 数量砍掉一半不做封装的做法是每个实体写四个 Servlet订单、药品、供应商、客户加起来十几个类每个类里都是重复的 getParameter、try-catch、forward。我一般让所有控制层类继承一个 BaseServlet页面通过 method 参数指定要调用哪个方法。package com.example.pharmacy.servlet; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; public class BaseServlet extends HttpServlet { Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method req.getParameter(method); if (method null || method.isEmpty()) { throw new ServletException(缺少 method 参数); } try { Method targetMethod this.getClass().getMethod( method, HttpServletRequest.class, HttpServletResponse.class); String result (String) targetMethod.invoke(this, req, resp); if (result null) { return; } if (result.startsWith(redirect:)) { resp.sendRedirect(result.substring(redirect:.length())); } else { req.getRequestDispatcher(result).forward(req, resp); } } catch (Exception e) { throw new ServletException(执行方法 method 失败, e); } } }这个类做了两件事第一通过反射找到当前 Servlet 里名为 method 参数值的方法并执行比如访问drugServlet?methodlist就自动调用 DrugServlet 类里的list(HttpServletRequest, HttpServletResponse)方法第二统一处理返回值支持 forward 和 redirect 两种跳转方式约定 redirect: 开头的是重定向其他的是转发。这样设计之后每个业务模块只需要写一个 Servlet 类类里放 list、add、update、delete 等方法代码量直接减半。反射在第一次调用时会有轻微性能损耗但在课设访问量下可以忽略。这个模式在后续学 SpringMVC 时也能看到它的影子能讲清楚反射分发的思路本身就是个加分项。4.3 药品入库与销售扣库存的事务控制核心业务别写崩存销系统最核心的代码是“生成单据的同时更新库存”。销售时先往 sale 表插一条数据再把 stock 表的 quantity 减掉这两个操作必须同时成功或者同时失败。如果插入成功但扣减失败库存就不对了反过来扣减成功但单据没生成账目也对不上。血泪经验告诉我们这类问题在演示时最容易暴露。package com.example.pharmacy.service; import com.example.pharmacy.dao.SaleDao; import com.example.pharmacy.dao.StockDao; import com.example.pharmacy.entity.Sale; import com.example.pharmacy.util.DBUtil; import java.sql.Connection; import java.sql.SQLException; public class SaleService { public void addSale(Sale sale) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); SaleDao saleDao new SaleDao(); StockDao stockDao new StockDao(); saleDao.insert(conn, sale); int updated stockDao.decreaseStock(conn, sale.getDrugId(), sale.getQuantity()); if (updated 0) { throw new SQLException(库存不足或药品不存在); } conn.commit(); } catch (Exception e) { try { if (conn ! null) { conn.rollback(); } } catch (SQLException ex) { ex.printStackTrace(); } throw new RuntimeException(销售出库失败: e.getMessage(), e); } finally { try { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } } }这段代码最重要的设计是 Connection 由 Service 层创建然后传给 DAO 层方法而不是每个 DAO 自己再去 getConnection。原因在于事务必须基于同一个连接才能控制如果 SaleDao 和 StockDao 各自开连接commit 和 rollback 就控制不到对方的操作。setAutoCommit(false)关闭自动提交后只有显式调 commit 才能把所有 SQL 一起生效。库存扣减那条 SQL 也是个技巧点UPDATE stock SET quantity quantity - ? WHERE drug_id ? AND quantity ?返回的 updated 行数为 0 就表示库存不足。这比先 SELECT 再判断再 UPDATE 少一次查询也避免了并发下的超卖问题。课设里不需要真的讨论高并发但能讲出“原子操作”这个思路答辩时已经超过大多数同学了。5. 运行与调试验收五个高频踩坑记录从现象到解决现场排掉5.1 中文乱码请求、响应、JSP 三处编码要统一现象表单提交中文到 Servlet 后request.getParameter 拿到的是乱码或者 JSP 页面显示中文变成问号。原因Tomcat 8 之后 GET 请求的 URI 编码默认是 UTF-8但 POST 请求的 body 编码取决于请求头里的 contentType如果页面和服务器有一处编码不统一就会乱。问题往往不在某个单点而是请求接收、响应输出、JSP 声明三处没有统一。解决写一个编码过滤器对所有请求和响应强制 UTF-8。package com.example.pharmacy.filter; import javax.servlet.*; import javax.servlet.annotation.WebFilter; import java.io.IOException; WebFilter(/*) public class EncodingFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(UTF-8); response.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); chain.doFilter(request, response); } }这里有三行设置各管一段request.setCharacterEncoding管后端接收参数时的解码格式response.setCharacterEncoding管响应内容写出的编码setContentType里的 charset 告诉浏览器用 UTF-8 解析页面。不要漏掉哪一行漏掉 request 那行表单提交的数据就是乱码漏掉 response 那行JSP 里 out.print 写出的中文就会花。同时 JSP 文件头部的 contentType 也要写charsetUTF-8三处对齐才保险。5.2 IDEA 部署后 404 / 500从 Artifact 和日志两头排查现象IDEA 里 Tomcat 正常启动控制台没报错但浏览器访问项目路径一直 404或者某个页面加载时报 500控制台没有任何异常输出。原因404 大概率是 Artifact 配置不对。IDEA 部署时访问的路径由 Deployment 里的 Application context 决定默认是/项目名_war_exploded如果浏览器访问的是http://localhost:8080/当然找不到页面。500 且控制台无输出很可能是 jar 包没打进 WEB-INF/libClassNotFoundException 被吞在页面渲染流程里。解决在 Run Configuration 的 Deployment 标签页把 Application context 改成/在 Project Structure 的 Artifacts 里把依赖 jar 拖进 WEB-INF/lib。然后看 Tomcat 日志确认真实原因不要对着浏览器猜。# 本地安装版 Tomcat 查看实时日志 tail -f $CATALINA_HOME/logs/catalina.out # 只看异常堆栈 grep -A 20 Exception $CATALINA_HOME/logs/catalina.outTomcat 日志是排错时最直接的依据ClassNotFoundException、端口被占用、部署失败都会记录在 catalina.out 里。IDEA 内嵌版 Tomcat 的日志输出在 Console 面板但 Console 经常被其他输出顶掉这时直接按时间点搜日志文件更靠谱。排查这类问题别当黑匣子猜先确认“请求到达了哪个组件”“类加载缺了什么”路径问题看 URL运行问题看日志。5.3 数据库连接失败驱动 jar、时区参数、MySQL 8 认证三个重灾区现象启动后访问列表页报 ClassNotFoundException或者报 SQLNonTransientConnectionException还有常见的The server time zone value错误。原因这三类报错的来源完全不同。ClassNotFoundException 是 mysql-connector-java 的 jar 没放到 WEB-INF/lib 下时区错误是连接串少了 serverTimezone 参数MySQL 8 认证问题则是因为默认的 caching_sha2_password 插件和旧版驱动不兼容。报错特征实际原因处理办法ClassNotFoundException: com.mysql.cj.jdbc.Driver驱动 jar 未部署把 jar 放进 WEB-INF/lib 并重新打包The server time zone valueurl 缺少时区参数连接串加 serverTimezoneAsia/ShanghaiPublic Key Retrieval is not allowedMySQL 8 认证插件连接串加 allowPublicKeyRetrievaltrueAccess denied for user密码或账号不对检查 db.properties 里的用户名密码时区问题最隐蔽因为它在本地开发时可能正常放到另一台机器就报错。连接串里加上serverTimezoneAsia/Shanghai是最通用的做法不要用 UTC否则时间会比北京时间差 8 小时。allowPublicKeyRetrievaltrue这个参数在 MySQL 8 下经常和 SSL 参数一起出现建议直接加进连接串避免部署到别的环境时又翻车。5.4 表单重复提交和双击提交用 PRG 模式把刷新拦截住现象新增药品时点了一次提交结果数据库里出现两条相同记录销售页面刷新一次库存被扣了两次。原因这是经典的表单重复提交问题。Servlet 处理完请求后如果用的是 forward 转发浏览器地址栏没有变化用户按 F5 时浏览器会重新提交上一次的 POST 请求于是同样的数据再处理一遍。事务只能保证一次提交的原子性救不了“第二次请求”因为第二次是一个全新的事务。解决采用 PRG 模式处理成功后重定向到列表页。在 BaseServlet 里已经写了转发和重定向的分流逻辑Service 层成功之后返回redirect:list这样的字符串即可。重定向之后地址栏会变成 GET 请求的列表 URL刷新页面只是重新查询不会重复提交。涉及新增和删除的请求都应该用重定向收尾查询请求用转发没关系。5.5 日期字段显示 null 或格式不对别依赖前端传时间现象入库单列表里 purchase_time 一列为空或者在 Java 端用 SimpleDateFormat 解析时抛 ParseException。原因页面表单里没有时间输入框提交时根本没有 time 参数后端拿到的就是 null。另一个情况是前端传了时间字符串但格式和 SimpleDateFormat 里写的不一致比如页面传的是2025-06-01代码却用yyyy-MM-dd HH:mm:ss去解析。解决数据库字段已经设置了 DEFAULT CURRENT_TIMESTAMP只要插入时不给该字段赋值MySQL 会自动写入当前时间。所以实体类里时间字段用LocalDateTime或java.util.Date接收时允许为空插入 SQL 里不列出这一列。如果确实需要在 Java 端生成时间直接new Date()并且统一用yyyy-MM-dd HH:mm:ss格式输出不要指望前端传。这一点在写 DAO 的 insert 方法时就要注意别把 null 传进去覆盖掉数据库默认值。6. 答辩前的四个增强让课设从“能跑”变成“能讲”代码能跑只是起点答辩时能把“为什么这么做”讲清楚才是高分关键。我建议在验收前补四个增强都是改动量小、展示效果明显的小功能。第一个增强是库存预警。在列表页上方加一个查询把低于预警线的药品筛选出来并标红。SQL 一句就能搞定SELECT d.drug_name, d.specification, s.quantity, s.warn_line FROM stock s JOIN drug d ON s.drug_id d.id WHERE s.quantity s.warn_line ORDER BY s.quantity ASC;JOIN关联出药品名称和规格WHERE过滤出低于预警线的记录。页面上用span stylecolor:red标出来整个系统立刻多了一个“管理”的味道而不只是增删改查。第二个增强是把 DBUtil 里的 DriverManager 换成数据库连接池推荐 Druid。只改工具类业务代码完全不用动。答辩时能讲出“每次 getConnection 都新建物理连接频繁创建销毁对数据库压力大连接池把连接复用起来”这个道理就已经体现你和只会照抄代码的同学之间的差距了。第三个增强是密码加密。admin 表里已经存了 MD5 值可以把 MD5Util 工具类补上登录时把用户输入的密码做一次加密再比对。如果时间充裕再提一嘴加盐比如MD5(password salt)把固定的盐写死在配置里即可。第四个增强是准备一张核心业务时序图。画一条“销售出库”的完整链路JSP 提交表单 → Servlet 接收参数 → Service 开启事务 → DAO 插入销售单 → DAO 扣减库存 → commit → redirect 回列表。这张图一放出来老师关于请求流程、事务控制的所有问题都不用临时组织语言。我做课设最大的教训是表结构没定稳就开始写 Java结果药品表加了两个字段DAO、Servlet、JSP 跟着改了三天。现在回头看先花一晚上把七张表和三条核心 SQL 的数据流理顺后面写代码就是填模板的事。希望帮到你。本文还有配套的精品资源点击获取
返回列表