ARTICLE DETAIL

资讯详情

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

JavaWeb药品管理系统实战:Servlet+JSP+JDBC全流程部署指南

JavaWeb药品管理系统实战:Servlet+JSP+JDBC全流程部署指南 简介本资源是一套面向Java初学者与高校实训学生的医院药品管理系统实战项目适用于JavaWeb课程设计、期末大作业及本科毕业设计。系统采用B/S架构基于Java语言开发融合JSP前端展示、Servlet业务逻辑与MySQL数据库持久化覆盖药品入库、库存查询、用户管理、权限控制等核心医疗管理场景助力学习者扎实掌握MVC分层开发与Web应用部署全流程。压缩包共422个文件含96个HTML页面、54个JS交互脚本、44个编译后Class文件、22个Java源码、14个Jar依赖库、10个CSS样式表及1个完整SQL建库脚本总大小10.21MB结构规范、模块清晰开箱即用。已有499人学习下载所有代码经严格调试可直接导入IDE运行配套数据库脚本一键初始化显著降低环境配置门槛是JavaWeb入门到进阶的高分实践范例。1. 这不是又一个“学生作业式”JavaWeb项目它真能跑通药品入库、出库、库存预警、采购计划生成全流程且数据库脚本已预置MySQL 5.7兼容结构适合刚学完ServletJSPJDBC但卡在「连不上数据库」或「增删改查总少一行SQL」的实战者你可能已经下载过十几个标着“JavaWeb医院管理系统”的压缩包解压后发现Tomcat启动报404、MySQL连接失败、登录页面空白、后台管理页点不动——最后默默删掉怀疑自己是不是不适合写Java。这个javaweb实训-基于javaweb的医院药品管理系统源码数据库.zip不是PPT演示工程也不是只跑通登录页的半成品。它包含完整可部署的三层结构表现层JSPServlet、业务层Service、数据访问层DAO数据库脚本hospital_drug.sql已按MySQL 5.7语法建好8张表药品表、供应商表、入库单、出库单、库存表、用户表、角色表、操作日志表所有SQL语句经实测通过含事务回滚逻辑和库存扣减原子性处理。它不依赖Spring Boot自动装配纯原生JavaWeb技术栈但做了关键加固连接池用DBCP2而非手动new ConnectionJSP中EL表达式全启用Servlet里统一处理中文乱码连最让人头疼的“药品名称带括号/斜杠导致入库失败”都加了字符串转义过滤。如果你正卡在“写了三天DAO层却始终查不到数据”或者想用真实业务场景比如按有效期自动标红近效期药品反向吃透JDBC事务与SQL注入防护这份资源就是为你留的调试靶场——不是教你怎么写Hello World而是教你写完之后怎么让系统在Tomcat里稳稳跑满一整天。2. 从零部署解压→配环境→启服务→验功能四步闭环验证是否真正“开箱即用”2.1 解压后目录结构解析看清哪些文件是“动不得”的核心资产哪些是“必须改”的配置入口解压javaweb实训-基于javaweb的医院药品管理系统源码数据库.zip后你会看到标准的JavaWeb项目结构hospital-drug-system/ ├── src/ # Java源码根目录 │ ├── com/hospital/drug/ # 主包名含servlet、service、dao、entity、util │ └── db.properties # 数据库连接配置关键必须改 ├── WebContent/ # Web资源根目录等同于传统WebRoot │ ├── WEB-INF/ │ │ ├── web.xml # Servlet注册与欢迎页配置勿删 │ │ └── lib/ # JAR包存放处含mysql-connector-java-5.1.47.jar、commons-dbcp-1.4.jar等 │ ├── index.jsp # 系统首页登录入口 │ ├── admin/ # 后台管理页面含药品管理、入库单、出库单等JSP │ └── css/js/images/ # 静态资源 ├── hospital_drug.sql # MySQL建库建表初始化数据脚本直接执行即可 └── README.md # 项目说明含默认账号密码提示src/db.properties和hospital_drug.sql是唯二必须操作的文件。其他所有.java、.jsp、.xml文件均无需修改即可运行——这是该资源区别于“教学Demo”的核心价值它把配置项收敛到最小集把业务逻辑固化在可验证路径上。2.2 JDKTomcatMySQL环境三件套版本选择有讲究错一个就卡在ClassNotFoundException该项目基于Java SE 8JDK 1.8编译严禁使用JDK 11。原因在于其Servlet API依赖javax.servlet.*包而JDK 9模块化后需额外引入javaee-web-api极易引发NoClassDefFoundError: javax/servlet/Servlet。Tomcat必须选8.5.x或9.0.x亲测Tomcat 8.5.93稳定因Tomcat 10已全面切换至Jakarta EE命名空间jakarta.servlet.*会导致所有Servlet类编译失败。MySQL推荐5.7.32hospital_drug.sql中建表语句使用ENGINEInnoDB DEFAULT CHARSETutf8MySQL 8.0默认字符集为utf8mb4虽兼容但需手动改表。安装顺序严格为安装JDK 1.8并配置JAVA_HOME安装Tomcat 8.5.x解压版即可无需安装版安装MySQL 5.7务必勾选“Add to PATH”启动MySQL服务用root账号登录验证mysql -u root -p。注意若你本地已装MySQL 8.0不要卸载——只需在db.properties中将jdbc.url末尾追加?serverTimezoneGMT%2B8useUnicodetruecharacterEncodingutf8并在执行hospital_drug.sql前手动创建数据库CREATE DATABASE hospital_drug CHARACTER SET utf8 COLLATE utf8_general_ci;2.3 数据库初始化执行SQL脚本时必须避开三个“静默失败”陷阱hospital_drug.sql文件包含建库、建表、插入初始数据三部分。常见失败并非语法错误而是环境适配问题-- 正确执行方式在MySQL命令行中逐行粘贴或用Navicat执行 CREATE DATABASE IF NOT EXISTS hospital_drug CHARACTER SET utf8 COLLATE utf8_general_ci; USE hospital_drug; -- 下面是建表语句省略具体字段重点看ENGINE和CHARSET CREATE TABLE drug_info ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, spec VARCHAR(50), unit VARCHAR(20), price DECIMAL(10,2), valid_period DATE, stock_quantity INT DEFAULT 0, supplier_id INT, create_time DATETIME DEFAULT NOW() ) ENGINEInnoDB DEFAULT CHARSETutf8; -- 插入测试数据含中文必须确保客户端编码为utf8 INSERT INTO drug_info (name, spec, unit, price, valid_period, stock_quantity, supplier_id) VALUES (阿莫西林胶囊, 0.25g*24粒, 盒, 12.50, 2025-12-31, 150, 1);关键参数说明ENGINEInnoDB必须指定否则事务控制失效入库/出库需事务保证库存一致性DEFAULT CHARSETutf8不是utf8mb4避免MySQL 5.7下utf8mb4不被识别DATETIME DEFAULT NOW()MySQL 5.7支持若用低版本需改为CURRENT_TIMESTAMP中文插入前先执行SET NAMES utf8;否则出现乱码导致登录失败。执行后验证USE hospital_drug; SELECT COUNT(*) FROM drug_info; -- 应返回≥5条初始含5种药品 SELECT user_name, password FROM sys_user WHERE role_id 1; -- 查管理员账号默认admin/1234562.4 Tomcat部署与启动别再盲目复制war包用IDEA直接部署才是调试效率关键不要把整个WebContent文件夹复制到Tomcat/webapps/下——这种做法无法热更新改一行JSP就要重启Tomcat。正确做法是在IntelliJ IDEA中配置Artifact打开项目 →File → Project Structure → Artifacts点击 → Web Application: Archive → For hospital-drug-system在Output Layout中确保WEB-INF/lib/下包含mysql-connector-java-5.1.47.jar数据库驱动commons-dbcp-1.4.jar连接池commons-pool-1.6.jarDBCP依赖jstl-1.2.jarJSP标签库Build → Build Artifacts → Build生成hospital-drug-system.warRun → Edit Configurations → → Tomcat Server → Local在Deployment选项卡中点击 → Artifact → hospital-drug-system:war exploded设置Application context为/drug访问地址变为http://localhost:8080/drug点击OK运行Tomcat。启动后观察控制台若出现INFO: Server startup in [xxx] ms且无红色ERROR则部署成功若报java.lang.ClassNotFoundException: org.apache.commons.dbcp.BasicDataSourceFactory说明commons-dbcp-1.4.jar未打入WAR包需检查Artifact配置若报com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown database hospital_drug说明数据库未创建或db.properties中URL写错。3. 核心业务流程走查从药品入库单提交到库存自动扣减手把手跟踪一条数据的全生命周期3.1 入库单提交链路JSP表单→Servlet接收→Service校验→DAO写库→事务回滚触发点以“新增药品入库单”为例完整调用链如下前端触发admin/stockin_add.jsp提交表单关键字段form actionStockInServlet methodpost input typetext namedrugId value1 !-- 药品ID -- input typetext namequantity value50 !-- 入库数量 -- input typedate namevalidPeriod value2025-12-31 !-- 有效期 -- input typesubmit value提交入库 /formServlet接收src/com/hospital/drug/servlet/StockInServlet.javaprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 获取参数已做request.setCharacterEncoding(UTF-8)防乱码 String drugId request.getParameter(drugId); String quantity request.getParameter(quantity); String validPeriod request.getParameter(validPeriod); // 2. 封装为StockIn对象 StockIn stockIn new StockIn(); stockIn.setDrugId(Integer.parseInt(drugId)); stockIn.setQuantity(Integer.parseInt(quantity)); stockIn.setValidPeriod(validPeriod); stockIn.setOperatorId(getCurrentUserId(request)); // 从session取当前登录人 // 3. 调用Service层关键开启事务 boolean result stockInService.addStockIn(stockIn); if (result) { request.setAttribute(msg, 入库成功); } else { request.setAttribute(msg, 入库失败请重试); } request.getRequestDispatcher(stockin_list.jsp).forward(request, response); }Service事务控制src/com/hospital/drug/service/impl/StockInServiceImpl.javaOverride public boolean addStockIn(StockIn stockIn) { Connection conn null; try { conn JDBCUtil.getConnection(); // 从DBCP连接池获取连接 conn.setAutoCommit(false); // 关闭自动提交开启事务 // 步骤1插入入库单主表 int rows1 stockInDao.insertStockIn(conn, stockIn); // 步骤2更新药品库存表库存 入库数量 int rows2 drugInfoDao.updateStockByDrugId(conn, stockIn.getDrugId(), stockIn.getQuantity()); if (rows1 0 rows2 0) { conn.commit(); // 两步都成功才提交 return true; } else { conn.rollback(); // 任一步失败则回滚 return false; } } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } e.printStackTrace(); return false; } finally { JDBCUtil.closeConnection(conn); // 归还连接到池 } }逻辑说明此处事务设计是本项目最大亮点。它没有用Spring声明式事务而是手动控制conn.setAutoCommit(false)确保“入库单记录”和“库存更新”要么全成功要么全失败。若updateStockByDrugId执行时因药品ID不存在抛出异常conn.rollback()会撤销已插入的入库单避免出现“单据存在但库存没加”的数据不一致。3.2 库存预警功能实现不是简单查count(*)而是用SQL计算“距效期≤30天”的药品集合系统首页右上角显示“近效期药品预警3”数字来自DrugInfoDao.getNearExpiryDrugs()方法public ListDrugInfo getNearExpiryDrugs() throws SQLException { String sql SELECT * FROM drug_info WHERE DATEDIFF(valid_period, CURDATE()) BETWEEN 0 AND 30 ORDER BY valid_period ASC; return queryRunner.query(sql, new BeanListHandler(DrugInfo.class)); }参数说明DATEDIFF(valid_period, CURDATE())计算有效期减去当前日期的天数BETWEEN 0 AND 30筛选剩余0~30天内过期的药品ORDER BY valid_period ASC按有效期升序排列最先过期的排最前BeanListHandlerDbUtils工具类自动映射结果集到DrugInfo对象列表。此SQL在MySQL 5.7下高效执行valid_period字段已建索引无需Java层遍历判断避免N1查询。若你本地MySQL时区非东八区需在db.properties中添加serverTimezoneGMT%2B8否则CURDATE()返回时间不准。3.3 采购计划生成逻辑基于历史出库量安全库存阈值自动生成补货建议点击“采购管理 → 生成采购计划”触发PurchasePlanServlet// 核心算法采购数量 MAX(0, 安全库存 - 当前库存 近30天平均日出库量 × 7) String sql SELECT d.id, d.name, d.spec, d.unit, d.stock_quantity AS current_stock, IFNULL(safe_stock, 0) AS safe_stock, IFNULL(avg_daily_out, 0) AS avg_daily_out, GREATEST(0, IFNULL(safe_stock, 0) - d.stock_quantity IFNULL(avg_daily_out, 0) * 7) AS purchase_qty FROM drug_info d LEFT JOIN ( SELECT drug_id, AVG(quantity) AS avg_daily_out FROM stock_out WHERE out_time DATE_SUB(CURDATE(), INTERVAL 30 DAY) GROUP BY drug_id ) t ON d.id t.drug_id WHERE d.stock_quantity IFNULL(safe_stock, 0) OR IFNULL(avg_daily_out, 0) 0;关键设计点DATE_SUB(CURDATE(), INTERVAL 30 DAY)精确截取近30天出库数据非模糊“一个月”GREATEST(0, ...)确保采购数量不为负数LEFT JOIN即使某药品30天内无出库记录仍会出现在结果中avg_daily_out为NULL转为0安全库存值存在drug_info.safe_stock字段默认为0需管理员在药品编辑页手动设置。生成结果页purchase_plan.jsp中每行显示药品名、当前库存、安全库存、建议采购量。点击“生成采购单”按钮会批量插入purchase_order表并关联purchase_order_detail明细表。4. 避坑指南这五个“看似正常却让系统静默崩溃”的经典问题我替你踩过了4.1 现象Tomcat启动成功但访问http://localhost:8080/drug显示404控制台无ERROR原因web.xml中welcome-file-list配置的首页文件名与实际不符。本项目index.jsp位于WebContent/根目录但web.xml中写的是welcome-file-list welcome-filelogin.jsp/welcome-file !-- 错应为index.jsp -- /welcome-file-list解决打开WebContent/WEB-INF/web.xml将welcome-file内容改为index.jsp保存后重启Tomcat。4.2 现象登录页输入admin/123456后跳转到空白页浏览器地址栏变为http://localhost:8080/drug/admin/index.jsp原因LoginServlet中response.sendRedirect(admin/index.jsp)路径错误。因项目Context Path为/drug相对路径admin/index.jsp会被解析为/drug/admin/index.jsp但实际JSP在WebContent/admin/index.jspTomcat找不到该路径。解决改为绝对路径重定向response.sendRedirect(request.getContextPath() /admin/index.jsp);4.3 现象药品管理页点击“编辑”弹出框显示药品信息全是null但数据库里数据正常原因DrugInfoDao.findById()方法中SQL语句字段名与实体类属性名不匹配。原始代码写String sql SELECT id,name,spec,unit,price,valid_period,stock_quantity,supplier_id FROM drug_info WHERE id?; // 但DrugInfo实体类中属性名为validPeriod驼峰而数据库字段为valid_period下划线 // DbUtils默认按属性名映射找不到valid_period字段故赋值为null解决两种方案任选其一方案A推荐在queryRunner.query()中使用new BasicRowProcessor(new GenerousBeanProcessor())自动转换下划线→驼峰方案B修改SQL为SELECT id,name,spec,unit,price,valid_period AS validPeriod,...显式别名。4.4 现象执行入库操作后库存数量没增加但入库单记录已写入数据库原因drug_info表中stock_quantity字段类型为INT但updateStockByDrugId()方法中SQL写成String sql UPDATE drug_info SET stock_quantity stock_quantity ? WHERE id ?; // 参数传入的是String类型50MySQL隐式转换失败实际执行为 stock_quantity stock_quantity 0解决确保DAO层参数为int类型queryRunner.update(conn, sql, Integer.parseInt(quantity), drugId);4.5 现象在IE11或Edge旧版浏览器中JSP页面中文显示为方块Chrome正常原因web.xml中未声明JSP页面默认编码。IE对% page contentTypetext/html;charsetUTF-8 %识别不稳定需在web.xml全局配置jsp-config jsp-property-group url-pattern*.jsp/url-pattern page-encodingUTF-8/page-encoding /jsp-property-group /jsp-config解决将上述配置块加入WebContent/WEB-INF/web.xml的web-app根节点内位置在welcome-file-list之后。5. 进阶技巧用Log4j埋点Druid监控把黑匣子式的JavaWeb请求变成可追踪的流水线5.1 在关键业务点添加Log4j日志让“谁在什么时候干了什么”一目了然项目已集成log4j-1.2.17.jar但默认只输出ERROR级别。要追踪完整请求链需在src/log4j.properties中调整# 设置根日志器为INFO级别输出到控制台和文件 log4j.rootLoggerINFO, stdout, file # 控制台输出开发阶段用 log4j.appender.stdoutorg.apache.log4j.ConsoleAppender log4j.appender.stdout.layoutorg.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m%n # 文件输出生产环境用 log4j.appender.fileorg.apache.log4j.DailyRollingFileAppender log4j.appender.file.Filelogs/app.log log4j.appender.file.layoutorg.apache.log4j.PatternLayout log4j.appender.file.layout.ConversionPattern%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m%n然后在StockInServlet.doPost()开头加入日志private static final Logger logger Logger.getLogger(StockInServlet.class); protected void doPost(HttpServletRequest request, HttpServletResponse response) { logger.info(【入库请求】用户ID getCurrentUserId(request) , 药品ID request.getParameter(drugId) , 数量 request.getParameter(quantity)); // 后续业务逻辑... }效果每次入库操作控制台立即打印2024-06-15 14:22:33 [http-nio-8080-exec-2] INFO StockInServlet - 【入库请求】用户ID1, 药品ID1, 数量50血泪经验曾遇到客户反馈“昨天下午三点入库没成功”翻日志发现该时段无任何【入库请求】记录立刻定位到是前端JavaScript拦截了表单提交——没有日志你只能靠猜。5.2 替换DBCP为Druid连接池获得SQL执行耗时、慢SQL告警、连接泄漏检测三大能力db.properties中当前配置为DBCPjdbc.driverClassNameorg.apache.commons.dbcp.BasicDataSource jdbc.urljdbc:mysql://localhost:3306/hospital_drug?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456替换为Druid需下载druid-1.2.16.jar放入WEB-INF/lib/# Druid配置替换原DBCP jdbc.driverClassNamecom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/hospital_drug?useUnicodetruecharacterEncodingutf8serverTimezoneGMT%2B8 jdbc.usernameroot jdbc.password123456 jdbc.initialSize5 jdbc.minIdle5 jdbc.maxActive20 jdbc.maxWait60000 jdbc.timeBetweenEvictionRunsMillis60000 jdbc.minEvictableIdleTimeMillis300000 jdbc.validationQuerySELECT 1 jdbc.testWhileIdletrue jdbc.testOnBorrowfalse jdbc.testOnReturnfalse然后修改JDBCUtil.java中的getConnection()方法// 原DBCP方式注释掉 // static { // try { // Properties props new Properties(); // props.load(JDBCUtil.class.getClassLoader().getResourceAsStream(db.properties)); // dataSource BasicDataSourceFactory.createDataSource(props); // } catch (Exception e) { // e.printStackTrace(); // } // } // 新Druid方式 private static DruidDataSource dataSource; static { try { Properties props new Properties(); props.load(JDBCUtil.class.getClassLoader().getResourceAsStream(db.properties)); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); // 开启SQL监控关键 dataSource.setFilters(stat,wall,log4j); } catch (Exception e) { e.printStackTrace(); } }Druid监控面板启用启动Tomcat后访问http://localhost:8080/drug/druid/index.html用户名admin密码admin可实时查看SQL执行列表含耗时、执行次数、参数绑定值慢SQL执行1秒自动标红数据源状态活跃连接数、等待线程数Web应用统计URI访问量、响应时间TOP10。后悔药某次上线后库存扣减变慢打开Druid发现UPDATE drug_info SET stock_quantity ? WHERE id ?平均耗时2.3秒。点开详情发现drug_info表缺索引——给id字段加主键索引后耗时降至15ms。没有Druid你得靠EXPLAIN一条条查至少浪费半天。5.3 用Filter统一处理中文乱码告别每个Servlet里写三行setCharacterEncoding当前项目在每个Servlet的doPost()开头都写request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8);这极易遗漏。新建src/com/hospital/drug/filter/EncodingFilter.javapublic class EncodingFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; // 统一设置编码 request.setCharacterEncoding(UTF-8); response.setContentType(text/html;charsetUTF-8); // 放行 chain.doFilter(request, response); } }并在web.xml中注册filter filter-nameEncodingFilter/filter-name filter-classcom.hospital.drug.filter.EncodingFilter/filter-class /filter filter-mapping filter-nameEncodingFilter/filter-name url-pattern/*/url-pattern !-- 拦截所有请求 -- /filter-mapping效果从此所有Servlet无需再手动设编码且Filter在Servlet之前执行确保getParameter()拿到的就是UTF-8解码后的中文。从那以后我每次接手JavaWeb项目第一件事就是加这个Filter——不是为了炫技而是因为漏写一次setCharacterEncoding就会导致“药品名称入库后变问号”而排查时你得翻遍所有Servlet比写十行业务逻辑还累。希望帮到你。本文还有配套的精品资源点击获取
返回列表