ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

药店管理系统JavaWeb课设拆解:从Servlet到MySQL的完整实战

药店管理系统JavaWeb课设拆解:从Servlet到MySQL的完整实战 简介这是一份基于JavaWebServletJSPMySQL的药店管理系统完整源码包定位为课程设计、毕业设计或Servlet/JSP初学者实践项目可部署于Tomcat容器运行。系统覆盖客户信息管理、药品类别管理、药品信息查询与出入库、出入库记录展示、用户管理及修改密码、退出登录等功能模块支撑药店日常运营的常见场景。压缩包共112个文件大小约2.19MB内含Java源码、编译后的class文件、JSP页面、数据库SQL脚本以及CSS/JS前端样式与Eclipse项目配置其中SQL脚本用于初始化MySQL数据项目结构清晰便于直接导入IDE。已有574人学习/下载适合需要参考完整增删改查、会话管理与数据库交互写法的学习者。通过阅读源码可快速理解Servlet处理请求、JSP展示数据、Dao层操作MySQL的基本流程也能基于现有模块扩展药品统计、报表等功能对提升JavaWeb项目实践能力有直接帮助。1. 药店管理系统这个 JavaWeb 课设是真的能跑通手里这份药店管理系统是典型的 JavaWeb 三层结构项目Servlet 做控制层、JSP 做页面、MySQL 存数据跑在 Tomcat 上。它不是那种只有前端页面的半成品而是把客户、药品类别、药品信息、出入库记录、用户权限这些模块都做全了的完整案例源码里能看到 LoginServlet、ProductSaveServlet、WareHouseSaveServlet 这些真实的业务处理逻辑还有一套建表用的数据库脚本。对正在做 JavaWeb 课程设计、毕业设计或者想找一份能二次开发的参考项目的人来说这份代码的参考价值在于它把「登录鉴权 → 数据展示 → 增删改查 → 出入库联动」这条完整链路跑通了不是教学 Demo 那种点到为止的写法。我拆这份资源的时候重点不是看它页面做得有多花哨而是看它怎么处理药品出库时「库存扣减」和「记录生成」这两件事的先后关系以及数据库表结构是怎么支撑起多模块联动的。这篇文章会把运行步骤、核心代码逻辑、参数设置和常见的坑都拆开讲照着走一遍你就能把这个系统跑起来。2. 先看清项目骨架目录结构、技术栈和数据库脚本的对应关系拿到源码包先别急着往 Tomcat 里丢。这个项目的目录结构是标准的 Eclipse JavaWeb 工程布局你要知道每个目录里装的是什么、和数据库里的表是怎么对应的后面改代码或者排查问题才不抓瞎。2.1 从 Eclipse 工程目录看整体结构整个项目的核心目录是src、WebContent和根目录下的数据库脚本文件。src里放的是 Java 源代码按功能看主要分三块实体类和 DAO 层负责跟 MySQL 打交道Servlet 负责接请求和跳页面还有一些工具类。WebContent下是 JSP 页面、静态资源和WEB-INF/web.xml配置文件。药店管理系统/ ├── src/ │ ├── com.xxx.dao/ // CustomerDao, ProductDao, WareHouseDao, UserDao, RecordDao │ ├── com.xxx.servlet/ // LoginServlet, ProductSaveServlet, WareHouseSaveServlet 等 │ └── com.xxx.entity/ // 实体类对应数据库表字段 ├── WebContent/ │ ├── login.jsp // 登录页 │ ├── customer/ // 客户管理相关 JSP │ ├── product/ // 药品信息相关 JSP │ ├── record/ // 出入库记录 JSP │ └── WEB-INF/ │ └── web.xml // Servlet 映射和欢迎页配置 ├── src 同级下的 Se122121rvlet.sql // 建库建表脚本注意文件名被截断过 ├── .classpath // Eclipse 构建路径 └── .project // Eclipse 项目标识CustomerDao、ProductDao、WareHouseDao、UserDao、RecordDao这五个 DAO 是数据访问的基座每个 DAO 对应一张或一组表。Servlet 层通过调用 DAO 完成业务不在 Servlet 里直接拼 SQL 操作数据库这种分离方式虽然简单但是分层清晰很适合用来理解 JavaWeb 工程的请求处理流程。提示Se122121rvlet.sql这个文件名明显被截断过拿到手后建议先把它重命名成pharmacy.sql之类的正常名字再导入避免 MySQL 命令行工具因为文件名问题读不到脚本。2.2 五个 DAO 类分别管什么表和业务的对应关系我从源码里把五个 DAO 类的作用理了一下它们和业务模块的对应关系是这样的DAO 类负责的实体核心方法对应的业务页面UserDao系统用户登录校验findUserByUsernameAndPwd()新增/删除用户登录、用户管理CustomerDao客户信息名单查询list()增/改/删客户管理列表ProductDao药品基本信息分页列表listByPage()add()/update()/delete()出库入库更新库存updateStock()药品信息管理WareHouseDao出入库记录插入记录addRecord()列出记录list()出入库记录RecordDao汇总查询按条件查记录记录列表页看到这里你可能会问出库和入库的动作为什么是ProductDao和WareHouseDao同时参与因为出库的本质是「药品表库存减少 出入库表新增一条出库记录」这两件事的组合。如果只有ProductDao改库存而WareHouseDao没插入记录那记录列表就会是空的反过来如果只写记录不改库存药品表的数据就永远不变。这套代码里ProductSaveServlet负责协调这两个 DAO 的调用顺序后面第 4 章会细讲。2.3 搭建运行环境JDK、Tomcat、MySQL 的版本搭配先把环境搭好再导入项目。按照这个项目的年代和技术栈推荐 JDK 8、Tomcat 8.5 或 9.0、MySQL 5.7 的组合。如果你电脑上装的是 MySQL 8.0也完全能跑但要注意mysql-connector-java驱动 JAR 包必须换成 8.x 版本而且驱动的类名要从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver这个坑不换直接用旧驱动跑 MySQL 8 会直接抛异常。数据库的创建流程是先在 MySQL 里建一个数据库比如pharmacy_db然后把 SQL 脚本导入进去。如果脚本开头有CREATE DATABASE语句你也可以直接整体导入。mysql -u root -p Se122121rvlet.sql导入后建议用use 数据库名;和show tables;确认一下表都建出来了。正常应该有用户表、客户表、药品类别表、药品信息表、出入库记录表这五类核心表。如果发现表数量不对多半是脚本的字符集或者版本兼容问题后面第 5 章会展开排查思路。2.4 在 Eclipse 里导入项目并跑起来的完整步骤Eclipse 导入这个项目的具体操作File → Import → General → Existing Projects into Workspace然后选中项目根目录即可。导入之后要做三件事把 MySQL 驱动 JAR 包放进WebContent/WEB-INF/lib如果没有的话检查 JDK 版本是否匹配最后确认 Tomcat 运行时已经配置好。然后是改数据库连接配置。这个项目的数据源信息一般写在 DAO 类顶部的静态变量里或者在db.properties配置文件里。找到DB_URL、DB_USERNAME、DB_PASSWORD这三个值改成你自己 MySQL 的账号密码private static final String DB_URL jdbc:mysql://localhost:3306/pharmacy_db?useUnicodetruecharacterEncodingutf-8useSSLfalse; private static final String DB_USERNAME root; private static final String DB_PASSWORD 123456;这里有个细节很容易栽连接字符串里的useSSLfalse和characterEncodingutf-8这两个参数不要删。前者是避免 MySQL 5.7 默认的 SSL 握手警告导致连接变慢后者是保证插入中文数据不乱码。如果你用 MySQL 8.0还得在DB_URL尾部加上serverTimezoneAsia/Shanghai否则 JDBC 驱动会因为服务器时区不明确直接拒绝连接。配置完成后在 Eclipse 里右键项目Run As → Run on Server启动浏览器访问http://localhost:8080/项目名/能跳到登录页就算项目跑通了。3. 登录鉴权和用户管理从 LoginServlet 看 Session 与请求转发这个系统的入口是登录页LoginServlet负责处理登录请求。这块逻辑看上去简单但它是理解整个系统权限控制的关键而且新手在这里出的问题特别多比如登录成功后页面跳不过去、刷新一下又回到登录页基本都是 Session 和请求转发没搞清楚。3.1 从 LoginServlet 走一遍登录流程登录页login.jsp把表单提交到LoginServletServlet 内部调用UserDao查询用户表判断用户名密码是否匹配。我拆源码时把核心流程整理成下面这个伪代码级别的流程不是原样抄代码但逻辑跟源码一致protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); UserDao userDao new UserDao(); User user userDao.findUserByUsernameAndPwd(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /productList.jsp); } else { request.setAttribute(loginError, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } }先说明这段代码里两个关键参数username和password是从表单的name属性取出来的如果 JSP 里input标签的 name 和这里不一致request.getParameter()就会拿到null那校验必败。loginUser是 Session 的键后面所有需要判断登录状态的页面都靠这个键。整个逻辑的关键在于——登录成功用的是sendRedirect重定向失败用的是forward转发。重定向会让浏览器重新发起一次请求地址栏变成目标页面转发是服务端内部的跳转浏览器感知不到。这两个用混了就会出现「登录成功后手动刷新页面变成 404」的问题。3.2 Session 控制和退出登录的实现细节这个系统的用户管理包括新增用户、删除用户和修改密码三个功能。新增用户走UserSaveServlet删除走UserDeleteServlet改密码则是单独的一个逻辑——通常是判断旧密码是否匹配再执行 UPDATE。这里放一段改密码的典型实现帮助你理解 DAO 和 Servlet 的配合// UserDao 中根据用户名修改密码 public boolean updatePassword(String username, String oldPassword, String newPassword) { String sql UPDATE tb_user SET password ? WHERE username ? AND password ?; // 使用 PreparedStatement 执行更新返回受影响行数 0 即成功 return executeUpdate(sql, newPassword, username, oldPassword) 0; }注意这里用了WHERE username ? AND password ?双重条件这种写法的好处是如果旧密码输入错误影响行数就是 0不用先查一遍用户记录再判断旧密码一个 SQL 就完成了校验和更新。这是老项目里常见的做法虽然简单但逻辑是闭合的。退出系统的实现一般是一个LogoutServlet核心动作是session.invalidate()把 Session 销毁然后重定向到登录页。有些人会误以为把 Session 里的loginUser移除就够了实际上invalidate()更彻底它能把这个会话相关的所有属性一次性清掉防止通过浏览器的「后退」按钮看到登录后才显示的页面内容。3.3 登录模块的常见误用拦截器没写在 web.xml这个项目的页面能在地址栏直接输入 URL 访问这就暴露了一个隐患——如果 JSP 页面本身没有判断 Session 是否为空的逻辑用户跳过登录直接访问页面路径也是能打开的。正规做法是在web.xml里配一个Filter拦截所有.jsp请求做 Session 校验。但这个老项目大概率没做这层防护它是一个「功能完整但安全基础」的课设型项目拷贝这份代码用来交作业没问题如果要上生产环境或者当毕设展示建议至少补一个登录过滤器代码很短但价值很大。4. 药品管理和出入库联动ProductDao 与 WareHouseDao 的库存扣减机制药品信息管理才是这个系统的核心模块理解这块代码等于理解了整个系统的设计骨架。它包括药品类别管理、药品信息增删改查、库存数量管理和出入库记录四块内容。其中最有讨论价值的是出入库操作它牵涉到两张表的同时更新这里的设计决策直接决定了库存数据会不会出差错。4.1 药品类别的数据模型与管理逻辑药品类别和药品信息之间是典型的一对多关系——一个类别下挂多个药品。数据库设计上药品表里有一个category_id外键关联类别表的id。类别管理模块提供的功能是新增类别、修改类别、删除类别、列出所有类别。删除类别时有一个需要注意的地方如果某个类别下还挂着药品直接删类别会导致药品表的category_id变成悬空引用查询药品列表时 join 不到类别名称页面上就会出现空白。所以严谨的删除逻辑是先查这个类别下有没有药品有的话提示用户先转移或删除药品。这种「先查子表再决定是否删除父表」的处理思路是这个模块里三个控制层类要处理的核心业务规则。如果说新增、修改、删除是基础 CRUD——这类简单操作的模式是统一的那么「删除前检查有没有关联数据」就属于业务约束项目里实现可能没那么严密但你要能看懂这个隐患。4.2 出库操作先扣库存还是先写记录先看出库的处理流程。前端页面提交一个出库表单包含药品 ID、出库数量、备注等字段请求到达ProductSaveServlet。我拆代码时把关键逻辑整理成下面这个顺序// 1. 根据药品 ID 查出当前药品 Product product productDao.findById(id); // 2. 校验库存是否充足 if (product.getStock() outCount) { // 返回错误页或提示信息不执行后续操作 } // 3. 扣减库存stock stock - outCount String updateStockSql UPDATE tb_product SET stock stock - ? WHERE id ? AND stock ?; int rows productDao.executeUpdate(updateStockSql, outCount, id, outCount); // 4. 只有扣减成功才插入出库记录 if (rows 0) { wareHouseDao.addRecord(new Record(null, id, 出库, outCount, remark, new Date())); }这段代码最值得学的点是UPDATE ... WHERE id ? AND stock ?这种条件更新的写法。它把「查询校验库存」和「扣减库存」合并成了一个原子操作——如果库存不足这个 UPDATE 的影响行数会是 0不会出现并发情况下先查到了库存充足但实际执行扣减时已经被其他请求抢走库存的情况。虽然这个课设里面大概率没考虑并发场景但 WHERE 子句带上stock ?这个条件是值得保留的好习惯。出库和入库的区别只是方向相反出库是stock - ?入库是stock ?记录表里用「出库」「入库」字符串来做标识区分。我在源码里注意到WareHouseSaveServlet和ProductSaveServlet都涉及出库入库实际运行时你可能发现两个 Servlet 有功能重叠这是不同版本迭代留下的痕迹不影响整体运行但读代码时要注意别绕晕。4.3 库存联动在代码里究竟改了几张表回答一个很多人会纠结的问题一次出库操作到底执行了几条 SQL如果上面的逻辑你没看明白我在这里总结清楚——一次出库 一条 UPDATE改药品表的库存字段 一条 INSERT写入库记录的明细。两条 SQL 不是在一个数据库事务里执行的这个设计有一定的风险如果第一条执行成功、插入记录时报错库存改了但记录没写成两边就对不上了。这个风险在课设演示基本碰不到因为单用户操作几乎不会有中间那一步报错的情况。但如果你要把它作为期末设计或者项目完善的方向「用事务包住这两条 SQL」会是答辩时一个很出彩的加分点。下章会提到这个问题的排查方式你实操时如果遇到「药品库存变了但记录列表里没有数据」的反常现象根源多半就在这里。4.4 药品信息的查询、新增、修改、删除的标准模板药品信息管理的 CRUD 部分是一套标准的 ServletJSPDAO 三层写法。列表页展示的思路是ProductDao里写一个分页查询方法接收pageNo和pageSize两个参数返回当前页的数据列表和总条数JSP 页面通过 JSTL 或纯 JSP 脚本循环渲染表格。我建议你重点读一遍ProductDao里分页查询的 SQL 写法。如果是 MySQL 的LIMIT offset, size写法那么在 SQL Server 或 Oracle 上要改成不同的写法。这套代码绑定 MySQL 语法所以它不能直接换到别的数据库上这是选型时就定死的边界。普通新增和修改操作的关键点在于表单参数要与Product实体类字段一一对应如果 JSP 里输入框的 name 和实体类属性不一致request.getParameter拿到后组装对象时就会漏字段。这种小问题导致的 bug不做断点调试很难发现。5. 避坑排查Tomcat 部署与 MySQL 连接的五个经典问题这个项目结构不算复杂但凡是 ServletJSP 的老项目运行起来总会碰到下面这些经典问题。我把拆代码和实际跑项目时遇到的坑整理成排查手册的形式每一条都是「现象 → 原因 → 解决」三步说清楚你遇到类似情况直接对号入座。5.1 404 报错访问路径总是差一个上下文名现象Tomcat 启动成功但是访问登录页时页面一直 404或者 CSS/JS 加载不出来。原因Eclipse 部署到 Tomcat 后项目的访问路径默认是http://localhost:8080/项目名/不是http://localhost:8080/。如果直接把 JSP 里的链接写成了/login.jsp这种绝对根路径浏览器会跑去根路径找文件自然找不到。解决JSP 里所有带路径的跳转和静态资源引用统一在头部加上${pageContext.request.contextPath}拼上下文路径。比如登录表单的 action 写成${pageContext.request.contextPath}/LoginServlet。如果你只是本地演示不想改页面也可以用另一个办法右键项目 → Properties → Web Project Settings把 Context root 改成/这样就能用根路径访问了不过要注意这样端口下只能部署一个项目。5.2 数据库连不上Access denied for user现象启动项目后页面报 500控制台打印Access denied for user rootlocalhost (using password: YES)。原因大概率是你把DB_USERNAME或DB_PASSWORD改成了自己 MySQL 的账号密码但密码里带了特殊字符比如、#、等。这些字符放在 URL 字符串里如果没做处理会破坏连接字符串的解析。解决密码里有特殊字符时优先用Properties文件存配置或用DriverManager.getConnection(url, user, password)的方法传参数——注意用户名和密码不要让 JDBC 内部再拼一次 URL。另外一个隐藏问题如果你用的是别人导出的 SQL 脚本脚本里可能自带CREATE USER rootxxx这种建用户语句导入后会把你的账号覆盖掉这个要特别留意。5.3 中文乱码插入数据库的数据全是问号或者乱码现象页面上正常输入中文提交后数据库里存的是???或者在 JSP 页面上显示出来是乱码。原因乱码问题的链条非常长链路中任何一环断了都会出问题。常见的有三种一是 JSP 文件本身的编码不是 UTF-8二是数据库表的字符集是latin1三是 Servlet 接收请求时没有设置request.setCharacterEncoding(UTF-8)。解决按顺序排查三步。第一步确认web.xml里没有配置 CharacterEncodingFilter 的话就在每个 Servlet 的doPost开头加request.setCharacterEncoding(UTF-8)和response.setContentType(text/html;charsetUTF-8)。第二步把 MySQL 数据库的默认字符集改成utf8mb4ALTER DATABASE pharmacy_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE tb_product CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;第三步检查 JDBC 连接串里有没有characterEncodingutf-8。这三步走完绝大多数乱码问题都能解决。5.4 启动秒退Tomcat 报 ClassNotFoundException 或 NoClassDefFoundError现象Tomcat 启动到一半直接失败控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。原因MySQL 驱动 JAR 包没放对位置。Eclipse 的 JavaWeb 项目里外部 JAR 必须是放在WebContent/WEB-INF/lib目录下而不是放在src下或者通过 Build Path 导入就完事了。如果你用的是 Maven 构建的 Tomcat 插件情况又不一样但这里不是 Maven 项目这是传统 Eclipse 工程。解决把mysql-connector-java-8.0.x.jar复制到WebContent/WEB-INF/lib目录然后在项目上右键 Refresh确保 Deployment Assembly 里包含这个目录。注意驱动类名也要跟着版本走MySQL 8.x 的驱动类名是com.mysql.cj.jdbc.DriverMySQL 5.x 才是com.mysql.jdbc.Driver两个版本驱动类名不能混用。5.5 时区问题Server returns invalid timezone现象项目能启动但只要一执行数据库查询后台就报The server time zone value xxx is unrecognized or represents more than one time zone。原因这是 MySQL 8.x 新增的时区校验机制旧版的 JDBC 驱动不会校验这个所以 MySQL 5.7 时代不需要设置时区。你本机 MySQL 是 8.0 的话必须明确指定时区否则驱动拒绝连接。解决在 JDBC URL 上追加serverTimezoneAsia/Shanghai。完整写法参考下面private static final String DB_URL jdbc:mysql://localhost:3306/pharmacy_db?useUnicodetruecharacterEncodingutf-8useSSLfalseserverTimezoneAsia/Shanghai;注意如果数据库服务器的系统时区不是中国时区但你在 URL 里写死了Asia/Shanghai查询出来的时间字段可能跟你预期有 1~7 个小时的偏差。这种时间偏差问题在测试时容易被忽略等真正分析数据时会发现时间轴对不上。6. 进阶改造把项目从 Eclipse 迁到 IDEA并给 DAO 换上 PreparedStatement如果你不只是想把项目跑起来交差而是想让它变成一份能写进简历的完整度更高的作品下面这两个改造方向性价比最高。它们不改变原有的 ServletJSP 架构却在稳定性和工程规范性上有质的提升。6.1 从 Eclipse 迁移到 IntelliJ IDEA 的关键步骤现在很多人的开发环境是 IntelliJ IDEA而不是 Eclipse。IDEA 的 JavaWeb 项目结构和 Eclipse 不同但迁移这个老项目其实并不难重点是别直接复制.project和.classpath这类 Eclipse 专属文件。我的迁移习惯是这样三步走第一步在 IDEA 里新建一个空的 JavaWeb 项目然后把src下的 Java 代码复制过来把WebContent下的内容复制到web目录第二步在 Project Structure 里把mysql-connector-java加到 Libraries然后配置 Artifacts 的 Lib 目录第三步配置 Tomcat 运行环境Deployment 里把项目部署进去Application context 填/pharmacy。真正容易翻车的点在 Artifacts 配置——IDEA 默认不会把WEB-INF/lib下的 JAR 包打包进去需要你在 Output Layout 里手动把 JAR 包拖进WEB-INF/lib。这一步漏了就又会看到那个ClassNotFoundException但这次不是驱动没下载而是驱动没被部署到运行时环境。6.2 把 Statement 换成 PreparedStatement一次替换终身受益如果你看过这个项目的 DAO 源码大概率会看到早期 JavaWeb 教材里经典的Statement.executeQuery(sql)写法即通过字符串拼接的方式把参数拼进 SQL。这种写法最大的问题不是性能而是安全——如果从页面上传入的参数包含 SQL 片段就可能被拼接出非预期的查询语句。我把常见的写法贴出来对比一下你自己就能判断为什么推荐升级// 不推荐字符串拼接 SQL参数面板注入风险 SQL 每次编译 String sql SELECT * FROM tb_product WHERE name productName ; Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql); // 推荐PreparedStatement 预编译 参数占位 String sql SELECT * FROM tb_product WHERE name ?; PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, productName); ResultSet rs ps.executeQuery();PreparedStatement的两个直接好处第一参数通过setString传入MySQL 驱动会自动做转义处理输入里的特殊字符不会破坏 SQL 结构第二SQL 只在第一次执行时编译循环执行同一条 SQL 时效率比Statement高。把项目里五个 DAO 的 SQL 替换成这个写法结构性改动会比较大但替换逻辑是机械性的。6.3 用 Filter 补一个登录拦截安全性的最小成本改造前面第 3 章提到这个系统没有登录拦截直接输入 URL 可以绕过登录页。想补上这个短板最经济的办法是写一个LoginFilter拦截除了LoginServlet和login.jsp之外的所有页面请求public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); if (session null || session.getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); }这段代码放在web.xml里配置一个filter-mappingURL 模式写成/*然后在filter里加上init-param排除登录相关路径。这个过滤器改造成本很低但在答辩时你就能理直气壮地说「系统增加了登录状态的统一校验」而不只是「前端页面隐藏了登录按钮」。从那以后我每次拿到一份老 JavaWeb 源码都会先做一遍这三件事——确认驱动版本、跑通数据库连接、顺手补一个登录过滤器——再做功能测试这套流程帮我过滤掉了不少隐藏极深的运行时问题。希望这次的拆解也能让你少走几步弯路。本文还有配套的精品资源点击获取
返回列表