简介基于JavaWeb的医院药品管理系统完整源码包面向正在完成java实训课程设计、期末大作业或毕业设计的计算机相关专业学生也适合需要项目实战练习的Java学习者。系统采用B/S结构以JSP、Servlet技术结合MySQL数据库实现涵盖药品信息维护、用户管理等核心模块项目经严格调试可直接运行。包内共422个文件约10.21MBjava源文件与class文件构成核心业务逻辑JSP、HTML及js、css负责前端展示与交互依赖库与数据库脚本保证环境快速部署另有gif、png等辅助材料。目录结构清晰便于按模块查阅。目前已有499人学习下载。附完整源码与数据库脚本读者还可获得整套可运行的项目示例、清晰的模块划分思路与分层实现方案便于课程设计答辩或毕业设计直接参考借鉴快速掌握管理系统开发流程。1. javaweb实训的医院药品管理系统先看清源码和数据库里到底有什么实训周最怕的不是写代码而是从网盘里拖下来一个“javaweb实训-基于javaweb的医院药品管理系统源码数据库.zip”之后不知道该从哪一步开始。解压后你通常会看到 src、web、sql、README 这几样东西这就是典型的 JavaWeb 课程设计形态JSP 做页面Servlet 接请求JDBC 或者 MyBatis 操作 MySQL最后整个项目扔到 Tomcat 里跑。它解决的问题很具体——药房需要维护药品基本信息、管好入库和出库、盯着库存别出错翻译成技术语言就是一套围绕“药品”和“库存”展开的增删改查系统。适合三类人实训作业还没着落的学生、想用最低成本看懂 JavaWeb 完整请求链路的入门者以及需要快速改造一套演示系统去应付答辩的开发。拿到手先别急着双击运行花十分钟把目录结构和数据库脚本过一遍后面每一步都踩得更稳。2. 从 zip 解压到 IDEA 跑通JavaWeb 骨架、依赖与 Tomcat 配置2.1 解压 zip 后先看结构判断这是 JSPServlet 还是 SSM很多人拿到压缩包的第一反应是解压完直接找 startup.bat结果双击之后浏览器开 localhost:8080 一片空白。实训项目一般不打 war 包它交付的是工程源码必须交给 IDEA 这类 IDE 去识别。先看目录结构常见做法是下面这种hospital_drug/ ├── src/ │ └── com/hospital/ │ ├── bean/ # 实体类对应数据库表 │ ├── dao/ # 数据访问层写 SQL 的地方 │ ├── servlet/ # 控制层接收请求并跳转页面 │ └── util/ # 数据库连接工具类 ├── web/ │ ├── WEB-INF/ │ │ ├── lib/ # 第三方 jar 包 │ │ └── web.xml # Servlet 注册与映射 │ ├── css/ js/ │ └── *.jsp # 页面文件 ├── sql/ │ └── hospital_drug.sql # 数据库脚本 └── README.txt # 部署说明这一眼扫过去就能判断技术路线如果 WEB-INF/lib 下只有 mysql-connector-java、jstl、c3p0 这类 jar就是经典的 JSPServletJDBC 三层结构如果出现了 spring、mybatis 的 jar或者 src 里有 mapper 接口和 Mapper.xml那才是 SSM 版本。判断清楚这个非常重要因为 IDEA 的配置方式在小细节上有差别。纯 Servlet 版本的 web.xml 里会直接写满 servlet 和 servlet-mappingSSM 版本则只有一个 DispatcherServlet 入口业务逻辑都跑到 Spring 容器里去了。这里还要纠正一个认知标题里的“数据库”指的几乎都是 sql 目录下的 .sql 脚本文件而不是内嵌的 MySQL 安装包。也就是说数据库环境要你自己装脚本要你自己导。这是个好消息——因为只要你会导 SQL整张数据表结构就完全透明后面写报告、画 E-R 图、改字段都有据可查。我一般拿到 zip 后第一件事不是看代码而是先打开 sql 脚本把表名和字段名抄出来确认管理员账号、药品表、库存表分别叫什么比在代码里漫无目的地找入口快得多。2.2 IDEA 运行 javaweb 项目配置从 New Project 到 Tomcat 启动配置步骤是实训项目里翻车率最高的环节而且看起来像玄学其实每一步都有明确目的。在 IDEA 里打开这个项目的标准流程是这样File → New → Project from Existing Sources选择解压出来的整个根目录比如 hospital_drug而不是点进 src。在 Project Structure → Project 里确认 SDK实训项目绝大多数用 JDK 1.8不要手贱去选 17 或 21老代码里有些写法在高版本 JDK 下会直接编译报错。右键 web 目录选择 Add Framework Support → Web Application让 IDEA 知道这是一个 Web 项目。Project Structure → Artifacts → 左上角 → Web Application: Exploded然后把 web 目录指定为 Web Resource Directory。这一步漏掉的话JSP 页面改了不生效甚至部署后找不到资源。Project Structure → Libraries → → Java选中 web/WEB-INF/lib把全部依赖 jar 加进来。不加这一步运行时会报 ClassNotFoundException。Run → Edit Configurations → → Tomcat Server → Local选择本机 Tomcat 路径在 Deployment 页签点 → Artifact把刚才配置的 exploded 包加进去。检查 Tomcat 的 conf/server.xml确认端口没被占用。其中第 4 步和第 5 步是最大的人的坑。很多人明明代码没问题启动也显示 Tomcat started浏览器偏偏 404九成是 Artifacts 没有关联 web 目录或者 lib 没打进去。端口配置长这样!-- tomcat/conf/server.xml -- Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /如果 8080 被其他程序占了把 port 改成 8088 或者 8081改完重启 Tomcat。访问地址就变成 http://localhost:8088/项目名/ 这种形态。项目名指的是 IDEA Deployment 页签里的 Application context它决定了 URL 前缀通常叫 /hospital_drug_war_exploded 之类也可以在 Edit Configurations → Deployment → Application context 里改成更短的 /hospital_drug这样访问路径干净很多。启动顺序也有讲究先启动 MySQL再启动 Tomcat。如果数据库没起来Tomcat 本身能正常启动但第一次访问登录页面时控制台会刷出一片 SQLException看起来像项目坏了其实只是数据库服务没开。这一步的经验很简单——启动 Tomcat 前顺手把 Navicat 或者命令行连一下 MySQL确认能连上再开浏览器。3. 数据库才是系统的命根子四张核心表设计与导入顺序3.1 从表名反推业务边界药品、库存、供应商、操作流水打开 hospital_drug.sql你会看到一套和药房业务对应的表结构。虽然每份源码的表名前缀可能不同有的叫 t_drug有的叫 tb_medicine但业务边界基本一致。这里整理一套最常见的表设计拿来当标准模板理解表名核心字段职责t_adminadmin_id, username, password, real_name系统登录用户t_categorycategory_id, category_name药品分类比如抗生素、中成药t_suppliersupplier_id, supplier_name, contact供应商信息t_drugdrug_id, drug_code, drug_name, specification, unit, retail_price, stock_quantity, expire_date药品主信息与当前库存t_in_recordrecord_id, drug_id, quantity, total_amount, operator_id, create_time入库流水t_out_recordrecord_id, drug_id, quantity, total_amount, patient_name, operator_id, create_time出库流水核心在 t_drug 表。设计里最该关注的是价格和库存这两个字段价格用 DECIMAL(10,2)不能只用 DOUBLE不然浮点误差会在期末验收时被老师一眼看出stock_quantity 是当前库存的冗余字段它虽然和流水表的数据有重复但实训查询场景多单独列出来能省掉大量 join 操作。表结构里为什么要有流水表因为药房要溯源——某个批次药品什么时候进的、谁操作的、出给了哪个患者流水表记录的就是这些事实这比只改一个库存数字严谨得多。还有一个细节如果表里有外键约束导入顺序必须符合依赖关系。先建分类表再建供应商表然后建药品表最后建流水表。MySQL 对自增主键和外键的管理比较宽容但一旦外键指向的表还没创建导入中途就会报错而且报错位置还不好定位。所以用 Navicat 或 DataGrip 导脚本时建议整个文件一次执行不要分段手工执行保证依赖顺序由脚本头部自动控制。3.2 一份 SQL 脚本的正确导法先建库再导表编码一步到位拿到手的 .sql 文件第一件事不是双击导入而是先看它开头有没有 CREATE DATABASE。常见做法是脚本头部自带建库语句比如下面这种-- 先建库指定 utf8mb4 字符集 CREATE DATABASE IF NOT EXISTS hospital_drug DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hospital_drug; -- 药品分类表 DROP TABLE IF EXISTS t_category; CREATE TABLE t_category ( category_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 分类ID, category_name VARCHAR(50) NOT NULL COMMENT 分类名称, remark VARCHAR(255) COMMENT 备注 ) ENGINEInnoDB COMMENT药品分类表; -- 药品信息表 DROP TABLE IF EXISTS t_drug; CREATE TABLE t_drug ( drug_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 药品ID, drug_code VARCHAR(20) NOT NULL COMMENT 药品编号, drug_name VARCHAR(100) NOT NULL COMMENT 药品通用名, specification VARCHAR(50) COMMENT 规格如 250ml/瓶, dosage_form VARCHAR(20) COMMENT 剂型片剂、胶囊、注射液, unit VARCHAR(10) COMMENT 最小计量单位, retail_price DECIMAL(10,2) COMMENT 零售价, stock_quantity INT DEFAULT 0 COMMENT 当前库存, category_id INT COMMENT 分类ID, supplier_id INT COMMENT 供应商ID, expire_date DATE COMMENT 有效期 ) ENGINEInnoDB COMMENT药品信息表;执行这套脚本时先确认 MySQL 服务在跑然后用命令行导入mysql -uroot -p hospital_drug sql/hospital_drug.sql如果没先建库命令里指定了一个不存在的库名会直接报 ERROR 1049。命令行导入前先执行 CREATE DATABASE或者用 Navicat右键连接 → 新建数据库 hospital_drug → 右键该数据库 → 运行 SQL 文件。整个过程注意字符集如果脚本开头是 utf8mb3 而 MySQL 8.0 默认是 utf8mb4导入中文后页面可能出现乱码这个要统一修改。3.3 修改表结构连带字段更新与数据回填的常用操作实训答辩时经常被临时问一个问题我现在要在药品表里加一个厂家字段这要怎么做这就涉及 DDL 中的 ALTER TABLE 操作。常见的几条如下-- 新增列 ALTER TABLE t_drug ADD COLUMN manufacturer VARCHAR(100) COMMENT 生产厂家; -- 修改列类型 ALTER TABLE t_drug MODIFY COLUMN retail_price DECIMAL(10,2) DEFAULT 0.00; -- 回填数据把老药品的厂家统一补上 UPDATE t_drug SET manufacturer 示例药业 WHERE manufacturer IS NULL; -- 删除不需要的列 ALTER TABLE t_drug DROP COLUMN batch_no;修改表结构之前一定先备份。这算是血泪经验——我见过有人直接 DROP TABLE 然后发现没备份只能重新导原始脚本之前手工录入的几百条测试数据全没了。实训项目虽然没有真实生产环境那么严肃但 DELETE 和 DROP 是没有后悔药的操作哪怕只导出一份 10KB 的 .sql 副本也比手抄数据强得多。ALTER 之后如果页面报错多半是 Java 实体类字段和数据库字段对不上检查一下 bean 里的属性和 JSP 里引用的字段名是否一致即可。4. 数据库连接池与增删改查必改参数和一条请求的完整路径4.1 数据库连接池db.properties 或 c3p0.xml 里的必改参数实训项目的数据库连接层通常集中在 util 包下的 DBUtil 类里。使用连接池的好处是每次页面请求不需要重新创建数据库连接而是从池里取空闲连接。如果代码里每次 new Connection 再关闭页面一刷新就卡顿这个现象在答辩演示时非常容易暴露。常见做法是用 c3p0配置集中在 src 下的 c3p0-config.xml 或 db.propertiesc3p0-config default-config property namejdbcUrljdbc:mysql://localhost:3306/hospital_drug?useSSLfalseamp;serverTimezoneAsia/Shanghaiamp;characterEncodingutf8/property property namedriverClasscom.mysql.cj.jdbc.Driver/property property nameuserroot/property property namepassword123456/property property nameinitialPoolSize5/property property nameminPoolSize5/property property namemaxPoolSize20/property property namemaxIdleTime60/property /default-config /c3p0-config拿到任何一份实训源码第一件事就是查这个配置文件里的四个必改项数据库 IP 和端口是不是 localhost:3306、库名是不是 hospital_drug、用户名是不是 root、密码是不是你本机的 MySQL 密码。前三个通常不用动密码是最容易踩空的——源码作者往往写他自己的密码你本地用户密码不一致的话Tomcat 能启动、页面也能打开但一执行登录就报 Access denied for user。连接池参数调的时候也有边界。initialPoolSize 和 minPoolSize 保持一致实训项目设 5 就够了maxPoolSize 不要盲目调大20 已经是合理上限调成 200 反而会让 MySQL 连接数暴涨一旦并发访问触发连接等待页面性能断崖式下跌。注意使用 MySQL 8.0 以上版本时driverClass 必须是 com.mysql.cj.jdbc.Driver老写法 com.mysql.jdbc.Driver 在新驱动里已经被移除了。MAX 还有 serverTimezone 参数不加这个参数 MySQL 8 驱动会报时区异常这是最常见的启动错误之一。4.2 药品列表的增删改查从 DAO 到 Servlet 再到 JSP 的完整链路增删改查是所有 javaweb 实训系统的核心药品管理系统的骨架也不例外。以“查询药品列表”为例你看懂这一条其他操作全是模板套改。先看 DAO 层public ListDrug findAll() { ListDrug list new ArrayList(); String sql SELECT drug_id, drug_name, specification, retail_price, stock_quantity FROM t_drug ORDER BY drug_id; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Drug d new Drug(); d.setDrugId(rs.getInt(drug_id)); d.setDrugName(rs.getString(drug_name)); d.setSpecification(rs.getString(specification)); d.setRetailPrice(rs.getBigDecimal(retail_price)); d.setStockQuantity(rs.getInt(stock_quantity)); list.add(d); } } catch (SQLException e) { e.printStackTrace(); } return list; }注意两点一是用 PreparedStatement 而不是 Statement既防止 SQL 注入也避免字符串拼接引号地狱二是 try-with-resources 写法连接、预编译语句、结果集在方法结束时自动关闭省得写一堆 finally。这套写法的执行过程是拿连接 → 预编译 SQL → 执行 → 遍历 ResultSet 映射到实体类 → 关闭资源返回列表。Servlet 层把数据送到页面protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DrugDao dao new DrugDao(); ListDrug drugList dao.findAll(); request.setAttribute(drugList, drugList); request.getRequestDispatcher(/drug/list.jsp).forward(request, response); }参数说明request.setAttribute 是把数据放进请求作用域注意不要用 session否则刷新页面数据不更新forward 是服务器内部跳转浏览器地址栏不会变这是 javaweb 实训要求学生必须掌握的请求转发和重定向区别。JSP 页面里用 JSTL 循环展示c:forEach items${drugList} vardrug tr td${drug.drugId}/td td${drug.drugName}/td td${drug.specification}/td td${drug.retailPrice}/td td${drug.stockQuantity}/td /tr /c:forEach页面能显示数据说明整条链路是通的。新增、删除、修改的区别只在 DAO 层换成 INSERT、UPDATE、DELETE 语句Servlet 层通过 action 参数区分调用哪个方法。所以你在源码里看到 drawer 的 doGet 和 doPost 时别被一长串 if/else 吓到本质上就是 CRUD 分发。4.3 表单提交与 web.xml 映射URL 是怎么一步步接上的页面按钮点了之后浏览器到底把请求发给了谁这由 web.xml 决定。JSP 里的表单 action 写法长这样form action${pageContext.request.contextPath}/drug/add methodpost input typetext namedrugName / input typetext nameretailPrice / input typesubmit value保存 / /form这里的 /drug/add 要和 web.xml 里的 servlet-mapping 对上servlet servlet-nameDrugServlet/servlet-name servlet-classcom.hospital.servlet.DrugServlet/servlet-class /servlet servlet-mapping servlet-nameDrugServlet/servlet-name url-pattern/drug/list/url-pattern url-pattern/drug/add/url-pattern url-pattern/drug/delete/url-pattern /servlet-mapping注意 URL-Pattern 里配的是不带项目前缀的路径而页面上用 ${pageContext.request.contextPath} 自动拼接项目上下文这样改 Application context 时页面不受影响。如果表单里漏掉 methodpost默认是 GET 提交页面上中文参数可能乱码而且地址栏会携带参数这也是新手常见翻车点之一。web.xml 里同时配多个 url-pattern 是合理的实训项目为了少用类常把多个操作塞进一个 Servlet靠 doGet 里的 action 参数进分支——代码可读性差一点但能跑答辩时被追问可以说清楚“这是为了演示请求分发逻辑”。5. 医院药品管理系统常见问题排查五条高频翻车记录5.1 登录报 Access denied for user先查配置里的密码而不是代码现象Tomcat 正常启动打开登录页输入账号密码控制台出现 java.sql.SQLException: Access denied for user rootlocalhost。原因几乎可以确定是数据库连接配置里的密码和本机 MySQL 实际密码不一致或者其他配置项指向了错误的库。解决打开 db.properties 或 c3p0-config.xml分别检查用户名、密码、URL 里的库名三项。先用 Navicat 或命令行测试同一组账号密码能不能连上数据库如果命令行能连而项目连不上再看 URL 里是否拼错了端口或库名。写完密码后必须重启 Tomcat连接池在启动时读配置光刷新页面没用。5.2 页面所有中文变成号字符集从建库到页面全链路排查现象登录进去之后药品名称、分类等中文内容全是问号英文正常。原因通常是三层中的某一层字符集没对齐MySQL 建库时用了 latin1、JDBC URL 没带 characterEncodingutf8或 JSP 页面没有声明 UTF-8。解决先确认建库语句里的 CHARSET 是不是 utf8mb4再确认连接 URL 末尾有没有 characterEncodingutf8最后看 JSP 首行是否写了 % page contentTypetext/html;charsetUTF-8 %。三个对齐后基本就恢复了。改动后刷新页面仍乱码的话清一次浏览器缓存前端静态资源经常被本地缓存干扰这不属于代码问题。5.3 Tomcat 启动成功但页面 404Artifacts 和 lib 二选一没配好现象控制台显示 Tomcat started访问 login.jsp 却直接 404或者 html 能打开但 jsp 404。原因在 IDEA 的 Artifacts 配置web 资源目录没有指到 web 文件夹或者 WEB-INF/lib 没有打进 artifact。解决打开 Project Structure → Artifacts在 Output Layout 里确认有 WEB-INF/classes 和 WEB-INF/lib且 lib 下能看到 mysql 驱动等 jar。如果列表为空手动右键 → Add Copy of → Module Output以及 Add Copy of → File 把 lib 整个加进去。这个配置容易在导入旧项目时丢掉属于 IDEA 层面的典型坑和代码本身无关。5.4 入库以后库存数量不对事务没开导致只执行一半现象做药品入库时点击保存页面提示成功进数据库发现入库流水表多了记录但 t_drug 表的 stock_quantity 没有增加或者增加了两次。原因常见于 DAO 里连续执行多条 UPDATE 时没有开启事务第一条 SQL 成功第二条异常被吞掉库存总数与流水对不上。解决多步写操作必须包在事务里。在 Service 层或 DAO 方法里把 autocommit 关掉全部成功再提交任何一步失败就回滚Connection conn DBUtil.getConnection(); try { conn.setAutoCommit(false); // 1. 向 t_in_record 插入流水 // 2. 更新 t_drug 的 stock_quantity conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); conn.close(); }注意 rollback 之后连接要放回池里并且把 autocommit 恢复为 true否则下一次从池里拿到这个连接时会带着旧的事务状态产生连锁的诡异错误。5.5 MySQL 8 驱动加载失败或时区报错换驱动和补参数一起做现象控制台报 ClassNotFoundException: com.mysql.jdbc.Driver或者 SQLNonTransientConnectionException 提示 serverTimezone 问题。原因基本是源码 lib 里带的 mysql-connector-java 是 5.x 老版本和本机 MySQL 8.x 不完全兼容。解决删除旧的驱动 jar从本地 Maven 仓库或官方路径获取 mysql-connector-java 8.x 版本放进 lib并把 driverClass 改成 com.mysql.cj.jdbc.Driver。同时确认 URL 里有 serverTimezoneAsia/Shanghai。常见做法是本地直接建一个 Maven 项目把这一个依赖导进去再拷出来比去官网翻下载快得多。两个不同版本的驱动 jar 同时放在 lib 下也会冲突旧驱动的 Class 被优先加载后新特性全不生效所以动手前先清干净。6. 从跑通到改造成绩药房管理员视角的验收清单6.1 按药房业务验收功能而不只是验收代码期末答辩时老师很少逐行读代码更常用业务提问来验证你是否真的理解系统。我习惯在跑通项目后先拿下面这张清单自查一遍比重复看代码效率高得多。验收项通过标准验证方式药品信息维护能新增、修改、删除药品列表实时刷新页面上操作后回数据库 SELECT 核对入库操作每次入库流水入库记录库存只增加一次连续入两单看 stock_quantity 增量出库操作库存只减少一次出库后不能为负数把库存出到 0 再操作看是否拦截药品有效期能看到过期日期字段列表或详情页直接展示操作日志管理员登录、库存变动可追溯流水表出现 operator_id如果系统里找不到上述某一块比如没有单独的库存表或没有流水记录那就要在答辩前想清楚自己的系统怎么回答“如何保证库存准确性”。实训项目不要求大而全但关联数据完整性能把你和那些只靠拼装页面交差的人区别开。6.2 两个可以立刻加分的增量验证库存预警与过期提醒在原有代码基础上加两个查询就能让演示效果上一个台阶。第一个是库存预警药品不足时列表高亮显示。SQL 并不复杂SELECT drug_name, stock_quantity FROM t_drug WHERE stock_quantity 20 ORDER BY stock_quantity ASC;第二个是 30 天内过期的药品检索。日期比较可以直接在 JSP 页面上做用 JSTL 里的 fmt 标签格式化当前日期再比对也可以在 SQL 里直接用日期函数SELECT drug_name, expire_date FROM t_drug WHERE expire_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY);这两个功能在业务上是药房真实存在的诉求代码改动量又小还能自然引出事务、索引、报表这些加分话题。答辩时被问到“你觉得还能怎么改进”把这两个点讲透比说自己“想加个大数据分析平台”可信得多。我改过不少实训项目价格字段用错精度、日期格式没统一、库存被改成负数这类问题出现频率最高。把这些坑在演示前全部压掉胜过临时抱佛脚改代码。希望你拿着这套清单能少走我当年走过的弯路顺利把这份 javaweb 实训项目跑通、改好、讲清楚——希望帮到你。本文还有配套的精品资源点击获取