news 2026/9/28 12:23:26

JavaWeb药店管理系统源码拆包:从环境搭建到进阶改造全指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb药店管理系统源码拆包:从环境搭建到进阶改造全指南

简介:这是一套面向Java Web初学者与课程设计者的药店管理系统完整项目,包含可运行源码与配套数据库,适合用于毕业设计、课程实训或自学练手。系统按MVC模式组织,覆盖用户管理、药品管理、销售管理、库存管理及报表统计等核心模块,并涉及Servlet、JSP、JSTL等Java Web技术,数据库部分提供药品表、用户表、订单表等设计,还包含权限控制与库存预警等实用逻辑。资源包共2493个文件,以js脚本、png与gif图片、css样式、html与jsp页面为主,另有java源码、class文件、jar依赖、xml配置及1个sql数据库脚本,压缩包约22.51MB,目录结构清晰,便于按模块查阅与二次开发。目前已有882人学习下载,读者可借此理解Web开发全流程、数据库设计与业务逻辑实现,积累项目经验。

1. 药店管理系统源码拆包:一套能跑通的 JavaWeb 全栈练手项目

如果你手头正好有一套基于 JavaWeb 的药店管理系统源码加数据库,却卡在“导入 IDEA 之后一堆红叉、Tomcat 起不来、数据库连不上”这一步,那这篇笔记就是写给你的。这套资源本质上是一个典型的 JavaWeb 全栈项目:Servlet + JSP + JDBC 打底,MySQL 存数据,MVC 分层组织代码,覆盖用户管理、药品管理、销售管理、库存管理几个核心模块。它不是什么高并发架构,但对课程设计、毕业设计、JavaWeb 入门到进阶的练手来说,模块完整、业务闭环清晰,属于“能真正跑起来看到页面”的那一类。适合谁?正在找 JavaWeb 完整案例、需要一套带数据库的增删改查实战、或者想拿它改造成自己课程设计的人。下面我按“先看懂结构、再动手跑通、最后避坑”的顺序拆一遍。

2. 源码结构与 MVC 分层:先看懂再动手改

拿到一套 JavaWeb 源码,最忌讳的就是直接双击打开然后到处点。先花十分钟把目录结构和分层逻辑理清楚,后面改代码、加功能才不会迷路。这套药店管理系统按 MVC 模式组织,理解它的分层是复现的第一步。

2.1 目录结构与各层职责

典型结构大致是这样,不同版本目录名可能略有差异,但分层逻辑一致:

src/ com/pharmacy/ controller/ // Servlet,接收请求、调 Service、转发视图 service/ // 业务逻辑,事务控制 dao/ // 数据访问,JDBC 操作数据库 entity/ // 实体类,对应数据库表 util/ // 数据库连接工具类、编码过滤器等 WebContent/ 或 web/ WEB-INF/ web.xml // Servlet 映射、过滤器配置 lib/ // 依赖 jar 包 jsp/ // 视图页面 css/ js/ images/ // 静态资源 sql/ pharmacy.sql // 建库建表脚本

Controller 层用 Servlet 接收 HTTP 请求,比如/medicine?action=list这种;Service 层放业务规则,比如“出库时先校验库存是否足够”;DAO 层用 JDBC 拼 SQL 操作 MySQL;entity 层就是药品、用户、订单这些 POJO。视图层是 JSP,负责把数据渲染成 HTML。

提示:如果源码里 DAO 层直接写了new MysqlDataSource()或者硬编码了数据库密码,先别急着骂,这是学生项目的常见写法,后面第 5 章会讲怎么改成配置文件。

2.2 数据库表设计与实体对应关系

数据库设计是这套系统的骨架。核心表通常包括药品表、用户表、订单表、订单明细表。下面是一个典型的药品表和订单表结构,字段名可能不同,但逻辑一致:

表名关键字段说明
medicineid, name, spec, price, stock, category药品基础信息,stock 用于库存预警
userid, username, password, rolerole 区分管理员和普通用户
ordersid, order_no, user_id, total, create_time订单主表
order_itemid, order_id, medicine_id, quantity, price订单明细,关联药品

实体类Medicine.java里的字段要和表字段一一对应,DAO 里用ResultSet取值时注意类型匹配。比如price用BigDecimal而不是double,避免金额计算出现浮点误差——这是很多学生项目翻车的地方。

2.3 从请求到响应的完整链路

以“查询药品列表”为例,走一遍链路你就明白各层怎么串起来:

  1. 浏览器访问/medicine?action=list
  2. MedicineServlet的doGet根据action参数判断操作类型
  3. 调用MedicineService.listAll(),Service 里可能加缓存或权限判断
  4. MedicineDao.findAll()执行SELECT * FROM medicine,返回List<Medicine>
  5. Servlet 把 list 塞进request.setAttribute("medicines", list)
  6. 转发到medicine_list.jsp,JSP 用 JSTL 的<c:forEach>遍历渲染
// MedicineServlet 核心片段 protected void doGet(HttpServletRequest request, HttpServletResponse response) { String action = request.getParameter("action"); if ("list".equals(action)) { List<Medicine> list = medicineService.listAll(); request.setAttribute("medicines", list); request.getRequestDispatcher("/jsp/medicine_list.jsp").forward(request, response); } }

这段代码的关键点是action参数做路由分发,一个 Servlet 处理多个操作。参数说明:request.getParameter("action")取 URL 上的 action 值,forward是服务器内部转发,浏览器地址栏不变。如果你改成redirect,request 里的属性就丢了,页面会空白——这是新手最容易踩的坑之一。

3. 环境搭建与数据库导入:把项目跑起来

看懂结构之后,下一步就是让它真正跑起来。这一章按顺序走:装环境、导数据库、配连接、起 Tomcat。每一步都有具体命令和参数,照着做基本能通。

3.1 JDK、Tomcat、MySQL 版本选择

版本不匹配是启动失败的头号原因。这套项目通常基于 Servlet 3.x/4.0,对应:

  • JDK:1.8 最稳,高版本 JDK 跑老项目容易遇到模块化报错
  • Tomcat:8.5 或 9.0,别用 Tomcat 10,因为包名从javax.servlet变成了jakarta.servlet,老代码直接编译不过
  • MySQL:5.7 或 8.0,8.0 要注意驱动包用com.mysql.cj.jdbc.Driver,URL 要加时区参数
# 检查 JDK 版本 java -version # 期望输出:java version "1.8.0_xxx" # 检查 MySQL 是否运行 mysql -u root -p -e "SELECT VERSION();"

如果 JDK 是 17 而项目用javax.servlet,要么降 JDK,要么手动加依赖。我一般建议直接切 JDK 8,省事。

3.2 导入 SQL 脚本与字符集设置

拿到pharmacy.sql之后,别直接双击运行。先看一眼文件头有没有CREATE DATABASE和USE语句,没有的话要手动建库。

# 登录 MySQL 后执行 CREATE DATABASE pharmacy DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE pharmacy; SOURCE /path/to/pharmacy.sql;

字符集必须用utf8mb4,因为药品名称里可能有生僻字或特殊符号。如果导入后中文变成乱码,检查三个地方:数据库字符集、连接 URL 的characterEncoding、JSP 页面的pageEncoding。三处统一成 UTF-8 基本能解决。

注意:MySQL 8.0 默认字符集已经是 utf8mb4,但 5.7 默认是 latin1,建库时不指定就会出乱码。

3.3 配置数据库连接与 IDEA 运行

数据库连接配置通常在util包下的DBUtil.java或BaseDao.java里。找到硬编码的 URL、用户名、密码,改成你自己的:

// DBUtil.java 典型写法 private static final String URL = "jdbc:mysql://localhost:3306/pharmacy?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"; private static final String USER = "root"; private static final String PASSWORD = "your_password"; public static Connection getConnection() throws SQLException { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } return DriverManager.getConnection(URL, USER, PASSWORD); }

参数说明:useUnicode=true&characterEncoding=utf8解决中文乱码;serverTimezone=Asia/Shanghai解决 MySQL 8.0 时区报错;useSSL=false去掉 SSL 警告。驱动类名 MySQL 8.0 用com.mysql.cj.jdbc.Driver,5.7 用com.mysql.jdbc.Driver,写错会报ClassNotFoundException。

IDEA 里配置 Tomcat:Run → Edit Configurations → 加 Tomcat Server → Deployment 里加 Artifact。如果 Artifact 是空的,去 Project Structure → Artifacts 里手动添加 Web Application Exploded。启动后访问http://localhost:8080/项目名/,能看到登录页就算成功。

4. 核心模块实现:用户、药品、销售、库存怎么串

环境跑通之后,重点看四个核心模块的实现逻辑。它们不是孤立的,销售会扣库存,库存预警会影响药品列表,用户权限控制着谁能操作什么。理解模块间的联动,才能改出自己想要的功能。

4.1 用户登录与权限过滤

登录模块的核心是 Session 存用户信息 + Filter 做权限拦截。登录 Servlet 验证用户名密码后,把 user 对象放进session.setAttribute("user", user)。然后写一个AuthFilter,拦截所有需要登录的路径:

// AuthFilter.java public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; HttpSession session = request.getSession(); String uri = request.getRequestURI(); // 放行登录页和静态资源 if (uri.contains("login.jsp") || uri.contains("login") || uri.contains("css/")) { chain.doFilter(req, res); return; } // 未登录跳转 if (session.getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/jsp/login.jsp"); return; } chain.doFilter(req, res); }

逻辑说明:Filter 在请求到达 Servlet 之前执行,先判断 URI 是否在白名单里,不在就检查 Session。参数request.getContextPath()拿到项目根路径,避免硬编码项目名。权限控制更细的话,可以在 Session 里存 role,Filter 里判断 role 是否允许访问该路径。

4.2 药品增删改查与库存预警

药品管理是典型的 CRUD,但库存预警是它的亮点。在药品列表页,当stock低于某个阈值时,用红色标记提醒。实现方式有两种:SQL 里加CASE WHEN stock < 10 THEN '预警' END,或者在 JSP 里用<c:if>判断。

-- 查询库存低于阈值的药品 SELECT id, name, spec, stock FROM medicine WHERE stock < 10 ORDER BY stock ASC;
// MedicineDao 里的更新库存方法 public void updateStock(int medicineId, int quantity) { String sql = "UPDATE medicine SET stock = stock - ? WHERE id = ? AND stock >= ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, quantity); ps.setInt(2, medicineId); ps.setInt(3, quantity); // 防止超卖 ps.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } }

关键点:WHERE stock >= ?是防止库存扣成负数。如果executeUpdate()返回 0,说明库存不足,Service 层要抛异常或返回错误信息。这个“乐观锁”思路在并发场景下能避免超卖,虽然学生项目并发不高,但养成习惯没坏处。

4.3 销售下单与库存联动

销售模块的流程是:选药品 → 生成订单 → 扣库存 → 记录明细。这四步必须在一个事务里,否则会出现“订单生成了但库存没扣”的脏数据。

// OrderService 下单方法 public boolean createOrder(Order order, List<OrderItem> items) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 orderDao.insert(conn, order); for (OrderItem item : items) { orderItemDao.insert(conn, item); medicineDao.updateStock(conn, item.getMedicineId(), item.getQuantity()); } conn.commit(); return true; } catch (Exception e) { if (conn != null) conn.rollback(); e.printStackTrace(); return false; } finally { if (conn != null) try { conn.close(); } catch (SQLException e) {} } }

逻辑说明:setAutoCommit(false)关闭自动提交,所有 DAO 操作共用同一个 Connection。任何一步失败就rollback(),保证数据一致性。参数上,DAO 方法要接收外部传入的 Connection,而不是自己getConnection(),否则事务控制不住。这是从“能跑”到“跑得对”的关键一步。

5. 避坑与排查:启动失败、乱码、连接池的常见问题

这一章全是血泪经验。我拆过不下十套 JavaWeb 项目,下面这几个坑几乎每套都会遇到,按“现象 → 原因 → 解决”整理,遇到问题直接对号入座。

5.1 启动报 ClassNotFoundException: com.mysql.jdbc.Driver

现象:Tomcat 启动后访问页面,控制台报驱动类找不到。 原因:MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver,老代码写的是com.mysql.jdbc.Driver;或者mysql-connector-java的 jar 包没放进WEB-INF/lib。 解决:改驱动类名,确认 jar 包在WEB-INF/lib下且被 IDEA 识别为库。Maven 项目检查pom.xml里依赖的 scope 是不是provided,是的话改成默认。

5.2 页面中文乱码,数据库里正常

现象:数据库里查出来是中文,页面上显示问号或方块。 原因:JSP 页面pageEncoding和响应Content-Type不一致,或者请求参数没设编码。 解决:JSP 头部统一写<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>;在 Filter 里加request.setCharacterEncoding("UTF-8");Tomcat 的server.xml里 Connector 加URIEncoding="UTF-8"。三处都改,基本根治。

5.3 Tomcat 端口占用或 404

现象:启动报Address already in use,或者访问 404。 原因:8080 端口被占;或者访问路径不对,没带项目名。 解决:netstat -ano | findstr 8080找到占用进程杀掉,或改 Tomcat 端口;404 检查 IDEA 的 Application context 是不是设成了/,访问时带对路径。

5.4 库存扣成负数

现象:并发下单或重复提交时,库存变成负数。 原因:先查库存再更新,两步之间没有锁;或者 SQL 没加stock >= quantity条件。 解决:用第 4.2 节那种UPDATE ... WHERE stock >= ?的写法,根据executeUpdate()返回值判断是否成功。重复提交可以在前端加 token 或后端做幂等。

5.5 JSP 里 Java 代码太多导致维护困难

现象:JSP 页面里<% %>脚本片段一大堆,改一个字段要翻好几个文件。 原因:视图层混入了业务逻辑,没有用 JSTL 和 EL 表达式。 解决:把逻辑挪到 Servlet 或 Service,JSP 只用<c:forEach>、<c:if>和${}取值。这是从“学生作业”到“能维护的项目”的分水岭。

6. 进阶改造:从能跑到能用的三个具体技巧

跑通之后,如果想让这套系统更像一个“正经项目”,有三个改造方向性价比最高。我一般会按这个顺序动手,每个都有具体代码和验证方法。

6.1 用 Druid 替换裸 JDBC 连接

裸 JDBC 每次请求都新建连接,性能差且容易耗尽连接数。换成 Druid 连接池,改一个工具类就行:

// DBUtil.java 改用 Druid private static DataSource dataSource; static { try { Properties props = new Properties(); props.load(DBUtil.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }

druid.properties里配url、username、password、initialSize、maxActive。验证方法:启动后连续访问几次,看日志里连接是否复用。参数maxActive别设太大,本地开发 10 就够。

6.2 把硬编码 SQL 换成 MyBatis

如果想让 DAO 层更清爽,可以引入 MyBatis。把MedicineDao的 JDBC 代码换成 Mapper 接口 + XML:

<!-- MedicineMapper.xml --> <select id="selectByStock" resultType="Medicine"> SELECT * FROM medicine WHERE stock &lt; #{threshold} ORDER BY stock ASC </select>
// Mapper 接口 List<Medicine> selectByStock(@Param("threshold") int threshold);

验证方法:写一个单元测试,调用selectByStock(10),看返回的 list 是否和直接跑 SQL 一致。MyBatis 的好处是 SQL 和 Java 分离,改查询不用动编译。

6.3 加一个库存预警的定时任务

库存预警如果只在查询时判断,管理员不刷新页面就看不到。加一个简单的定时任务,每天检查一次并写日志或发通知:

// 用 ScheduledExecutorService 做简单定时 ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(() -> { List<Medicine> lowStock = medicineService.getLowStock(10); if (!lowStock.isEmpty()) { System.out.println("库存预警:" + lowStock.size() + " 种药品低于阈值"); } }, 0, 24, TimeUnit.HOURS);

参数说明:scheduleAtFixedRate的第二个参数是首次延迟,第三个是间隔。生产环境可以用 Quartz 或 Spring Task,但练手项目用 JDK 自带的就够。验证方法:把阈值调高,看控制台是否输出预警信息。

从那以后我每次拿到一套 JavaWeb 源码,都强制先跑通数据库连接再动代码,不然改了半天发现是环境问题,白费功夫。希望这套拆解能帮你少走点弯路,顺利把药店管理系统跑起来、改下去。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 12:23:26

U-Net医学图像分割代码包实战:从跑通到多类别调优

简介&#xff1a;这份资源面向医学图像分割、语义分割与多类别分割的学习者和研究者&#xff0c;提供一套基于U-Net的完整代码实现。U-Net凭借对称的收缩与扩展路径以及跳跃连接&#xff0c;能在小样本数据下捕捉上下文信息并保留精细边界&#xff0c;适合疾病诊断、病变定位与…

作者头像 李华
网站建设 2026/9/28 12:22:01

2026专科生AI论文平台实测:从选题到降AI率的完整指南

1. 为什么2026年专科生论文仍然这么难&#xff1a;三大死穴与AI的切点先说个现象。每年三到五月&#xff0c;我后台收到最多的不是考研咨询&#xff0c;而是专科生的论文求助。有人拿着只写了三百字的开题报告问我能不能代写&#xff0c;有人把学校查重系统的截图甩过来&#x…

作者头像 李华
网站建设 2026/9/28 12:19:35

Maven依赖版本管理实战:插件命令与升级回滚全攻略

上个月我接了一个维护了四五年的老项目&#xff0c;打开 pom.xml 扫了一眼&#xff0c;好家伙&#xff0c;一半第三方依赖都停留在三年前的版本。问了下前任维护的同学&#xff0c;回复很直接&#xff1a;“能用就不动&#xff0c;怕升挂了。” 这话听着没毛病&#xff0c;但真…

作者头像 李华
网站建设 2026/9/28 12:17:10

基于Suricata的NIDS毕设骨架:源码拆解与调优实战

简介&#xff1a;一份基于Suricata的轻量级网络入侵检测系统毕业设计demo&#xff0c;包含完整可运行的源码与项目说明文档&#xff0c;面向网络工程、信息安全、计算机等相关专业学生&#xff0c;适用于课程设计、期末大作业或毕设参考。压缩包共2000个文件&#xff0c;以C源码…

作者头像 李华
网站建设 2026/9/28 12:17:06

Few-shot视线估计复现全攻略:从环境搭建到模型调优

简介&#xff1a;一套面向毕业设计的视线估计&#xff08;gaze estimation&#xff09;few-shot 项目源码&#xff0c;核心工作为复现并优化 Seonwook Park 的 few_shot_gaze 方法&#xff0c;并基于 MPIIFaceGaze 与 GazeCapture 两大公开数据集完成训练与评估。压缩包共93个文…

作者头像 李华
网站建设 2026/9/28 12:15:25

基于C#的MES加工装配模拟系统开发实战

简介&#xff1a;基于C#的工厂MES加工装配模拟系统源码包&#xff0c;面向毕业设计选题与工业信息化方向学习者&#xff0c;定位为可直接运行、二次开发与教学演示的完整项目。系统以制造执行为核心&#xff0c;覆盖生产订单管理、物料需求计划、生产调度、设备状态监控、质量控…

作者头像 李华