基于JavaWeb的药品存销信息管理系统:数据库设计与工程实现

发布时间:2026/9/28 12:56:42
基于JavaWeb的药品存销信息管理系统:数据库设计与工程实现 简介这是一套面向高校软件工程课程大作业的完整项目资料基于JavaWeb技术栈实现药品存销信息管理系统适合正在准备课程设计、毕业设计或需要JavaWeb实战练手的学生与开发者参考。压缩包共623个文件约46.67MB其中108个java源文件与202个class文件构成核心业务逻辑69个jar包提供框架与数据库驱动依赖65个xml与7个properties负责配置管理36个jsp、30个js和22个css搭建前端交互界面另有sql脚本、xls与xlsx数据表及日志文件辅助理解运行过程。资源涵盖药品入库、库存管理、销售记录、管理员维护等典型模块目录结构清晰便于按功能模块拆解学习。目前已有99人学习下载可作为课程答辩的参考方案也能帮助读者掌握JavaWeb分层开发、数据库设计与前后端联调思路快速搭建可运行的药品存销管理系统。1. 药品存销管理系统从课程大作业到能跑通的 JavaWeb 工程很多软件工程专业的同学在拿到「基于 JavaWeb 的药品存销信息管理系统」这个大作业题目时第一反应是去搜一套完整案例照着改。但真正动手才会发现网上流传的所谓完整案例要么只有 JSP 页面没有像样的数据库设计要么数据库脚本和实体类字段对不上导入 IDEA 之后连 Tomcat 都起不来。这个标题背后其实包含三件事一套能增删改查的药品进销存业务逻辑、一个可导入的 MySQL 数据库、一份能在本地跑起来的 JavaWeb 工程。它适合正在做课程设计的学生也适合想用一个小型业务系统练手 JavaWeb 全流程的开发者。我见过太多人卡在「数据库连不上」和「页面 404」这两个环节其实问题往往出在 JDBC 驱动版本、连接池配置和 web.xml 的 servlet 映射上。把这三处理顺整个系统就能从一堆源码变成一个真正能录入药品、能查库存、能出库的可用工具。2. 药品存销系统的数据库设计先定表结构再写代码2.1 四张核心表撑起进销存主流程药品存销信息管理系统的业务本质是「药品基础信息 入库 出库 库存」的闭环。我一般会先画四张表药品信息表drug_info、入库记录表stock_in、出库记录表stock_out、库存表inventory。药品信息表存药品编号、名称、规格、生产厂家、单价、有效期这些静态属性入库和出库表分别记录每一次流转的药品 ID、数量、操作时间、经办人库存表则是药品 ID 和当前数量的对应关系通过入库和出库操作来增减。为什么不把库存直接做成药品表的一个字段因为一旦需要查「某药品这个月出了多少」单表字段就无能为力了。拆成流水表加库存表既能追溯每一笔操作又能快速查当前库存。这是进销存类系统的通用做法也是数据库课程设计里常考的「范式与反范式权衡」。下面是我常用的建表 SQL字段类型和约束都经过实际导入验证-- 药品信息表 CREATE TABLE drug_info ( drug_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 药品编号, drug_name VARCHAR(100) NOT NULL COMMENT 药品名称, spec VARCHAR(50) NOT NULL COMMENT 规格, manufacturer VARCHAR(100) COMMENT 生产厂家, unit_price DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 单价, expire_date DATE COMMENT 有效期至, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品基础信息; -- 入库记录表 CREATE TABLE stock_in ( in_id INT PRIMARY KEY AUTO_INCREMENT, drug_id INT NOT NULL COMMENT 关联药品编号, quantity INT NOT NULL DEFAULT 0 COMMENT 入库数量, in_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 入库时间, operator VARCHAR(50) COMMENT 经办人, FOREIGN KEY (drug_id) REFERENCES drug_info(drug_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT入库流水; -- 出库记录表 CREATE TABLE stock_out ( out_id INT PRIMARY KEY AUTO_INCREMENT, drug_id INT NOT NULL COMMENT 关联药品编号, quantity INT NOT NULL DEFAULT 0 COMMENT 出库数量, out_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 出库时间, operator VARCHAR(50) COMMENT 经办人, FOREIGN KEY (drug_id) REFERENCES drug_info(drug_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT出库流水; -- 库存表 CREATE TABLE inventory ( drug_id INT PRIMARY KEY COMMENT 药品编号, stock_num INT NOT NULL DEFAULT 0 COMMENT 当前库存数量, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (drug_id) REFERENCES drug_info(drug_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT实时库存;建表时有两个参数值得注意。一是字符集用 utf8mb4 而不是 utf8因为药品名称里可能出现生僻字或特殊符号utf8 在三字节以上字符会报错。二是 DECIMAL(10,2) 存单价不要用 FLOAT 或 DOUBLE浮点数在金额计算时会出现 0.10.2≠0.3 的经典问题对账时就是血泪教训。外键约束建议保留虽然有些教程说互联网项目不用外键但课程设计场景下数据一致性比性能更重要外键能帮你挡住脏数据。2.2 库存增减用事务保证一致性入库和出库操作必须同时写流水表和更新库存表这两步要么都成功要么都失败。我见过有同学先 insert 流水再 update 库存结果 update 时报错流水里多了一条记录但库存没变对账时怎么都对不上。正确做法是用 JDBC 的事务控制Connection conn null; PreparedStatement ps1 null; PreparedStatement ps2 null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 // 1. 插入入库流水 String sql1 INSERT INTO stock_in(drug_id, quantity, operator) VALUES(?,?,?); ps1 conn.prepareStatement(sql1); ps1.setInt(1, drugId); ps1.setInt(2, quantity); ps1.setString(3, operator); ps1.executeUpdate(); // 2. 更新库存存在则累加不存在则插入 String sql2 INSERT INTO inventory(drug_id, stock_num) VALUES(?,?) ON DUPLICATE KEY UPDATE stock_num stock_num ?; ps2 conn.prepareStatement(sql2); ps2.setInt(1, drugId); ps2.setInt(2, quantity); ps2.setInt(3, quantity); ps2.executeUpdate(); conn.commit(); // 两步都成功才提交 } catch (Exception e) { if (conn ! null) conn.rollback(); // 任一步失败则回滚 e.printStackTrace(); } finally { DBUtil.close(conn, ps1, ps2); }这里用ON DUPLICATE KEY UPDATE是 MySQL 的特性一条语句搞定「有则更新无则插入」比先 select 再判断再 insert/update 少一次数据库往返。参数stock_num stock_num ?里的问号就是本次入库数量这样写避免了并发场景下先读后写导致的数量覆盖。事务的setAutoCommit(false)和commit()/rollback()必须成对出现finally 里只关连接不提交否则事务控制就失效了。3. 在 IDEA 里跑通 JavaWeb 工程从导入到页面可访问3.1 项目结构与依赖配置一套典型的 JavaWeb 药品存销系统目录结构是这样的src 下分 dao、service、servlet、entity 四个包WebContent或 webapp下放 jsp 页面和 WEB-INF/web.xml。导入 IDEA 后第一件事是检查 Project Structure 里的 Modules 和 Artifacts。常见翻车点是 artifact 里没有把 lib 目录下的 jar 包加进去导致运行时 ClassNotFoundException。依赖方面核心就三个MySQL 驱动mysql-connector-java、Servlet APIservlet-api.jarscope 设为 provided、JSTL 标签库jstl.jar standard.jar。如果项目用了连接池还会多一个 druid 或 c3p0 的 jar。版本上MySQL 8.x 必须用 8.0 以上的驱动驱动类名从com.mysql.jdbc.Driver变成了com.mysql.cj.jdbc.DriverURL 也要加时区和 SSL 参数# db.properties 数据库连接配置 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/drug_stock?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password你的密码serverTimezoneAsia/Shanghai不加会报时区错误useSSLfalse避免本地开发时的 SSL 警告allowPublicKeyRetrievaltrue是 MySQL 8 默认认证插件 caching_sha2_password 需要的。这几个参数少一个都可能连不上建议直接抄。3.2 Tomcat 配置与 Servlet 映射IDEA 里配 Tomcat 的步骤Run → Edit Configurations → 加一个 Tomcat Server → Local → 在 Deployment 里加 Artifact。Application context 建议设成/drug这样访问路径就是http://localhost:8080/drug/。端口默认 8080如果被占用改成 8081 也行但要注意代码里如果有硬编码的 URL 也要同步改。Servlet 映射有两种方式web.xml 配置和注解。老项目多用 web.xml新项目用WebServlet(/drugList)注解更简洁。如果两种混用注意不要给同一个 Servlet 配两次否则启动时报「Servlet 已注册」的错误。web.xml 里还要配 CharacterEncodingFilter 解决中文乱码filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping如果项目没用 Spring就自己写一个 Filter 实现javax.servlet.Filter接口在doFilter里调request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)。这个过滤器要放在所有 Servlet 之前生效url-pattern 用/*拦截全部请求。3.3 从登录页到药品列表的完整链路验证部署成功后先访问登录页。登录逻辑一般是查 user 表比对用户名密码成功后把用户信息放进 session然后重定向到药品列表页。这里有个容易忽略的点重定向用response.sendRedirect()转发用request.getRequestDispatcher().forward()。登录成功要用重定向否则刷新页面会重复提交表单查询结果展示用转发因为要把 request 里的数据带到 JSP。药品列表页通常用 JSTL 的c:forEach遍历% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % table trth药品编号/thth名称/thth规格/thth库存/thth操作/th/tr c:forEach items${drugList} vardrug tr td${drug.drugId}/td td${drug.drugName}/td td${drug.spec}/td td${drug.stockNum}/td tda hrefdrugEdit?id${drug.drugId}编辑/a/td /tr /c:forEach /table${drugList}是 Servlet 里request.setAttribute(drugList, list)传过来的。如果页面显示空白先看 Tomcat 控制台有没有 SQL 异常再看 JSP 里 EL 表达式有没有拼错。我一般会在 Servlet 里加一行System.out.println(查询到 list.size() 条药品)确认数据层没问题再排查视图层。4. 避坑与排查药品存销系统最常见的五个翻车点4.1 数据库连不上驱动、URL、权限三查现象是启动 Tomcat 后访问任何页面都报 500控制台里Communications link failure或Access denied for user。原因通常有三个驱动 jar 没放进 WEB-INF/lib、URL 里没加时区参数、MySQL 用户没有远程或本地权限。解决方法是先确认mysql-connector-java的 jar 在 artifact 的 lib 目录下然后检查 db.properties 里的 URL 是否包含serverTimezone最后用mysql -u root -p能登录说明密码没错如果还不行就执行GRANT ALL ON drug_stock.* TO rootlocalhost;刷新权限。4.2 中文乱码从请求到响应全链路排查现象是药品名称存进数据库变成问号或者页面显示乱码。原因可能出在数据库字符集、连接 URL 字符集、JSP 页面编码、响应头编码四个环节。解决顺序是先确认建表时用了 utf8mb4再确认 URL 里有characterEncodingutf8mb4然后 JSP 顶部加% page contentTypetext/html;charsetUTF-8 %最后在 Filter 里设置 request 和 response 的编码。四个环节缺一个都会乱码我一般写一个编码过滤器统一处理避免在每个 Servlet 里重复写。4.3 库存扣成负数并发下的超卖问题现象是出库时没检查库存或者检查了但两个请求同时通过检查导致库存扣到负数。原因是「查询库存 → 判断是否足够 → 扣减」这三步不是原子的。解决方法是在 SQL 的 update 语句里加条件UPDATE inventory SET stock_num stock_num - ? WHERE drug_id ? AND stock_num ?然后判断executeUpdate()的返回值如果返回 0 说明库存不足回滚事务并提示用户。这样把判断和扣减合并成一条原子语句就不存在并发窗口了。4.4 JSP 页面 404路径与映射的常见错位现象是登录页能打开点登录按钮后 404。原因通常是 form 的 action 路径写错了比如写了/login但 Servlet 映射的是/user/login或者项目 context path 是/drug但 action 写成了/drug/login而实际映射是/login。解决方法是统一用${pageContext.request.contextPath}/login这种写法让容器自动补上 context path。另外检查 web.xml 里 servlet-mapping 的 url-pattern 和 form action 是否完全一致大小写敏感。4.5 连接池耗尽连接没关的连锁反应现象是系统运行一段时间后所有数据库操作都超时重启 Tomcat 又恢复正常。原因是 DAO 层某个方法忘了关 Connection每次请求泄漏一个连接池子满了就再也拿不到连接。解决方法是在 finally 块里统一关闭 ResultSet、PreparedStatement、Connection顺序不能反。如果用 Druid 连接池可以在配置里加removeAbandonedtrue和removeAbandonedTimeout180让池子自动回收超过 3 分钟没关的连接但这只是兜底根本还是要修代码。5. 进阶技巧用一条 SQL 查出药品库存与近期出库量课程设计做完基础增删改查之后如果想拿高分或者让系统更实用可以加一个「药品库存与出库统计」页面。核心是一条聚合查询把药品信息、当前库存、近 7 天出库总量一次查出来SELECT d.drug_id, d.drug_name, d.spec, IFNULL(i.stock_num, 0) AS stock_num, IFNULL(SUM(o.quantity), 0) AS out_last_7d FROM drug_info d LEFT JOIN inventory i ON d.drug_id i.drug_id LEFT JOIN stock_out o ON d.drug_id o.drug_id AND o.out_time DATE_SUB(NOW(), INTERVAL 7 DAY) GROUP BY d.drug_id, d.drug_name, d.spec, i.stock_num ORDER BY out_last_7d DESC;这条 SQL 的关键在于 LEFT JOIN 加条件。LEFT JOIN stock_out保证没有出库记录的药品也会出现在结果里AND o.out_time DATE_SUB(NOW(), INTERVAL 7 DAY)把时间过滤放在 JOIN 条件里而不是 WHERE 里这样不会把没有出库的药品过滤掉。IFNULL处理库存表里还没有记录的药品显示为 0。GROUP BY里要把所有非聚合列都列上MySQL 的ONLY_FULL_GROUP_BY模式下少列一个就报错。在 Java 层这个查询结果可以映射到一个 VO 类字段包括 drugId、drugName、spec、stockNum、outLast7d。Servlet 里调 DAO 拿到 List转发到 JSP 用表格展示。如果想再进一步可以在页面上加一个「库存预警」标记当 stockNum 小于某个阈值时显示红色。阈值可以硬编码成 10也可以做成配置项存在数据库里。我自己的习惯是每做完一个模块就用 Postman 或浏览器直接调 Servlet 的 URL看返回的 JSON 或页面内容是否符合预期而不是等整个系统做完再联调。这样出问题时范围小排查快。另外数据库脚本一定要单独存一个 .sql 文件每次改表结构都同步更新别只改本地数据库不更新脚本换台机器就抓瞎。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询