☰
SSM药店管理系统课程设计:从数据库设计到部署全流程解析
2026/10/11 21:18:06 网站建设 项目流程

每年课程设计的高峰期,SSM 药店管理系统都是 Java Web 方向出现频率最高的题目之一。它表面看只是一个普通的药品增删改查,背后却把 Spring、SpringMVC、MyBatis、MySQL、Tomcat 这一整套经典技术栈串了起来,还配套源码、数据库脚本、调试部署说明和万字论文文档。对准备做课程设计、毕业设计的同学来说,这种系统最大的价值不是“能跑”,而是能通过它把 Java Web 项目从前端页面到数据库落库,完整走一遍。这篇文章就围绕这套系统,从业务需求拆解、数据库设计、核心代码实现,到本地调试部署和论文文档组织,把整个链路用实际经验讲透,希望能帮你少踩几个坑。

1. 系统设计与技术选型:药店管理系统到底要解决什么问题

1.1 药店真实业务场景里的核心需求

很多人第一次接触药店管理系统,以为核心就是“药品列表增删改查”,这就把问题想简单了。药店的实际业务链条是:采购员从供应商进货,仓库验收后录入批次和效期,药品上架后由店员销售给顾客,同时库存要实时扣减,快到效期的药品要提醒,库存低于安全值要补货,月底还要统计这个月进了多少、卖了多少、赚了多少。任何一个环节数据对不上,月底盘点的账就对不上,这就是药店管理系统最核心的存在意义。

从业务场景反推,系统的功能模块其实非常清晰:药品档案管理、供应商管理、客户管理、采购入库、销售出库、库存查询与预警、统计报表、用户登录与权限控制。听起来不少,但每个模块的实现难度并不高。真正需要花心思的是模块之间的数据联动,尤其是“一次销售同时扣减库存”“一次入库同时增加库存写流水”这种跨表操作,一旦漏了事务,系统就会出现数据不一致。

1.2 为什么这套系统用 SSM 而不是 Spring Boot

现在新项目确实都用 Spring Boot,课程设计里出现 SSM 并不是因为它落后,而是因为 SSM 能更好地展示 Java Web 的分层思想。用 SSM 的时候,你得自己配置 Spring 容器、自己写 SpringMVC 的控制器映射、自己维护 MyBatis 的 Mapper 映射文件,这个过程能把“IoC 容器是什么”“AOP 事务切到哪一层”“前端请求怎么被分发到 Controller”这些概念真正搞明白。Spring Boot 把这些都自动化了,开发快,但是答辩的时候反而讲不出太多东西。

而且从技术栈成熟度看,SSM 项目的资料太全了,几乎任何一个报错都能在社区里找到同类问题。对新手来说,遇到问题“能搜到答案”比“用新版技术栈”重要得多。这套系统的具体技术选型是:Spring 负责对象管理和事务,SpringMVC 负责请求路由,MyBatis 负责 SQL 映射,MySQL 存数据,Tomcat 作为运行容器,前端用 JSP + JSTL + Bootstrap 做页面,构建工具用 Maven,数据库连接池用 Druid。整套组合足够经典,也足够稳定。

1.3 从标题里的“程序+源码+数据库”倒推项目结构

标题里提到的“程序+源码+数据库+调试部署+开发环境”,其实就是一套课程设计项目的标准交付内容。程序指的是编译后可运行的项目包,源码是完整的工程文件,数据库是建表脚本和初始化数据,调试部署则是整个环境搭建的过程文档。我见过不少同学拿到这种项目后,第一件事就是双击打开某个文件夹,看能不能直接运行,结果很容易失败。

正确的做法是先把项目解压,确认里面的目录结构。典型的 SSM 项目会包含:pom.xml、src 目录、resources 配置文件目录、sql 脚本文件夹、以及说明文档。先搞清楚每个目录是干什么的,再按数据库初始化、配置文件修改、启动部署的顺序操作,这样就比盲目点运行稳得多。

2. 数据库设计:系统好不好用,建表的时候就决定了

2.1 核心业务表的梳理与关系设计

药店管理系统的数据库不需要做得很复杂,但表之间的关系一定要清楚。我按业务习惯把表分成几组:系统用户表存登录账号;药品信息表存药品档案;供应商和客户表存往来单位;入库单主表、入库单明细表、销售单主表、销售单明细表存业务流水;库存数量可以直接冗余在药品表里,也可以单独建库存表。课程设计里大部分项目直接把库存字段放在药品表,这样查询简单,页面展示也方便。

表之间的关系方面,药品和供应商是多对一,一张供应商表对应多条药品记录;药品和销售单据之间是多对多,因为一个销售单里可以包含多个药品,一个药品也会出现在多个销售单里,这种关系必须通过销售单明细表来拆开。如果直接把药品名称和数量塞进销售单表的一个字段,后面统计“某个药品卖了多少”就会非常痛苦。这一点在数据库设计答辩的时候经常被问到,建议提前准备好话术。

2.2 药品信息表字段设计的关键细节

药品表是整个系统的核心,字段设计直接决定后面的代码好不好写。我建表时一般这样定义:

CREATE TABLE `drug_info` ( `drug_id` int NOT NULL AUTO_INCREMENT COMMENT '药品ID', `drug_code` varchar(50) NOT NULL COMMENT '药品编码', `drug_name` varchar(100) NOT NULL COMMENT '药品名称', `specification` varchar(100) DEFAULT NULL COMMENT '规格', `unit` varchar(20) DEFAULT NULL COMMENT '单位', `manufacturer` varchar(100) DEFAULT NULL COMMENT '生产厂家', `produce_date` date DEFAULT NULL COMMENT '生产日期', `expire_date` date DEFAULT NULL COMMENT '有效期至', `purchase_price` decimal(10,2) DEFAULT NULL COMMENT '进货价', `sale_price` decimal(10,2) DEFAULT NULL COMMENT '销售价', `stock_quantity` int DEFAULT 0 COMMENT '当前库存', `warning_value` int DEFAULT 10 COMMENT '库存预警值', PRIMARY KEY (`drug_id`), UNIQUE KEY `uk_drug_code` (`drug_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='药品信息表';

这里有几个细节值得注意。药品编码必须加唯一索引,否则系统里可能出现两条名称相同但编码不同的药品记录,后面统计时容易重复。价格字段必须用 decimal,不能直接使用 float 或 double,浮点数的精度在实际开发中会带来“账目差一分钱”的尴尬问题。库存预警值一定要单独建字段,很多新手只在页面上写死一个“库存小于10就提醒”,但不同药品的合理库存水平完全不同,急救药和慢病药要分开设置。

2.3 为什么业务流水表和库存冗余不能互相替代

药品表里的库存字段属于冗余设计,但它不能替代流水表。任何一个系统,库存数字都应该是“结果”,而不是“事实的源头”。比如销售单明细表记录了今天卖出了哪几盒阿莫西林,那么库存扣减就是根据销售单计算出来的结果。如果只留一个库存字段,月底想统计“本月感冒药卖了多少钱”,完全没有数据支撑;想过期损耗想追溯到哪一批药出了问题,也查不到。

所以入库和销售的流水表一定要保留。入库单主表记录入库单号、供应商、入库日期、操作人、总金额,入库单明细表记录每一种药品的入库数量和单价。销售单同理,主表记录单号、客户、销售日期、操作人、总金额,明细表记录每种药品的销售数量和售价。后面做报表统计的时候,所有的数据都从这几张流水表聚合,药品表里的库存字段只是为了方便页面实时展示才存在的。

2.4 数据库连接池与配置文件的常见写法

数据库连接这块,课程设计里常用的连接池有 Druid 和 C3P0,推荐用 Druid,因为监控页面方便,配置也不复杂。数据库配置文件一般放在 resources 目录下,比如 jdbc.properties:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/drug_store?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456

如果你的 MySQL 是 5.7 以下版本,驱动类名可以写com.mysql.jdbc.Driver,MySQL 8.0 以上就必须用com.mysql.cj.jdbc.Driver,同时加上 serverTimezone 参数,否则连接时会报时区错误。这个配置是调试部署环节最常出问题的地方,后面我会专门展开讲。

3. 核心代码实现:从登录到库存预警的完整链路

3.1 三层架构的包结构怎么组织

一套标准的 SSM 项目,包结构最好按照 controller、service、mapper、entity 来划分。实体类放 entity,对应数据库表;Mapper 接口和 Mapper XML 放 mapper;Service 接口和实现类放 service;Controller 放 controller。另外可以建一个 common 或 util 包,放拦截器、工具类、分页插件配置等。这样划分的好处不只是看着整齐,更重要的是答辩时画架构图、讲调用链非常方便。

我在实际调试中见过不少反面教材,业务逻辑全部写在 Controller 里,或者 JSP 页面里直接查数据库。这种写法在小项目里看起来能跑,但一旦要加一个“库存不足不能销售”的规则,就得在多个 Controller 里重复修改,代码很快就会失控。所以哪怕只是做课程设计,也建议保持 Controller-Service-Mapper 的调用关系:Controller 只接收参数和返回视图,Service 写业务规则,Mapper 只做 SQL 交互。

3.2 登录认证、验证码与权限拦截

药店管理系统虽然没有特别复杂的权限要求,但登录功能几乎是必备的。使用 SSM 实现登录,最常见的方案是:用户提交用户名密码,Service 层校验后把用户信息放进 Session;然后定义一个拦截器,拦截除登录页、静态资源之外的所有请求,每次请求前判断 Session 里有没有用户对象,没有就重定向到登录页。

拦截器的写法大致是这样:

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login"); return false; } return true; } }

然后在 SpringMVC 配置里注册拦截路径。这种方案比在每个 Controller 方法里手动判断登录状态要省事得多,代码也不会重复。关于验证码,课程设计里用简单的随机数字图片验证码就够了,注意验证码生成时要存在 Session 里,登录校验时先取 Session 里的值比较,比较完无论对错都要删除,避免同一个验证码被重复使用。

3.3 药品管理模块的增删改查与模糊查询

药品管理是整个系统最基础的功能,涉及列表展示、新增、编辑、删除、模糊查询。Controller 里的方法设计一般是一套固定套路:列表方法返回 list 页面,新增方法接收表单参数后调用 Service 保存,编辑方法根据 ID 查出记录回显到页面,删除方法根据 ID 删除并重定向回列表。这个模块虽然简单,但它能锻炼对 REST 风格 URL 和数据绑定的理解。

MyBatis 的 Mapper XML 里,模糊查询是必考内容。查询条件有药品名称、编码、厂家等,因为条件可能为空,所以要使用动态 SQL:

<select id="listDrug" resultType="com.example.entity.DrugInfo"> SELECT * FROM drug_info <where> <if test="drugName != null and drugName != ''"> AND drug_name LIKE CONCAT('%', #{drugName}, '%') </if> <if test="drugCode != null and drugCode != ''"> AND drug_code LIKE CONCAT('%', #{drugCode}, '%') </if> </where> ORDER BY drug_id DESC </select>

注意这里的参数绑定一定要用#{},不能直接拼字符串。#{}会生成预编译 SQL,能有效防止 SQL 注入,这也是面试时必问的一个点。如果使用了 PageHelper 分页插件,只需要在查询之前调用PageHelper.startPage(pageNum, pageSize),后面的查询会自动带上 limit,非常方便。

3.4 库存预警与销售单创建的事务处理

库存预警的实现从代码角度来说很直接:写一个查询方法,条件是库存数量小于等于预警值。WHERE stock_quantity <= warning_value,查出来后在页面上用红色或橙色标出。前端展示部分建议加筛选条件,让页面可以分别显示“正常药品”“库存不足”“全部”,不然药品一多,整个列表全是红色,预警就失效了。

销售模块是事务处理的重点。一次销售要同时做三件事:写入销售单主表、写入销售单明细表、扣减药品库存。这三件事必须在一个事务里完成,否则可能出现“主表写入成功但库存没扣”的严重 bug。Spring 的声明式事务很简单,在 Service 方法上加@Transactional注解即可:

@Transactional public boolean createSale(SaleOrder order, List<SaleOrderItem> items) { saleOrderMapper.insert(order); for (SaleOrderItem item : items) { saleOrderItemMapper.insert(item); drugInfoMapper.decreaseStock(item.getDrugId(), item.getQuantity()); } return true; }

这里还有一个容易忽略的细节:扣减库存的 SQL 一定要带上库存充足条件,例如:

UPDATE drug_info SET stock_quantity = stock_quantity - #{quantity} WHERE drug_id = #{drugId} AND stock_quantity >= #{quantity}

如果影响行数为 0,说明库存不足,此时应该抛出业务异常让事务回滚。只做前端判断“库存是否够”是不够的,并发场景下必须靠 SQL 条件来兜底。这个点是系统在并发场景下能不能真正可用的关键,也是面试和答辩时最能体现水平的细节之一。

4. 调试部署全流程:让系统在一小时内跑起来

4.1 开发环境版本搭配

SSM 项目对环境版本非常敏感,版本不匹配是跑不起来的首要原因。我调试这类项目时推荐的组合是:JDK 1.8、Tomcat 8.5、Maven 3.6.3、MySQL 5.7 或 8.0、IDEA 2021 及以上版本、Navicat 作为数据库可视化工具。

这里重点强调几个兼容性坑。Tomcat 10 之后把所有 javax 命名空间改成了 jakarta,老的项目如果用的是原版 SSM 依赖,部署到 Tomcat 10 会直接报 ClassNotFound 之类的错误,老老实实用 Tomcat 8 或 9 最省事。MySQL 版本如果高于 8.0,连接驱动必须换成mysql-connector-java 8.0.x,同时 JDBC URL 里加serverTimezone=Asia/Shanghai。IDEA 启动项目时如果出现 “java: 错误: 不支持发行版本 5” 这类提示,多半是项目编译级别和当前 JDK 版本不一致,在 Project Structure 里把 SDK 和语言级别都改成 1.8 即可。

4.2 导入项目并初始化数据库的具体步骤

假设你已经拿到了一套完整的项目文件夹,里面包含源码和 SQL 脚本,我建议按下面的顺序操作,每一步都确认没问题再走下一步。

第一步,把项目压缩包解压到一个没有中文和空格的路径下。Windows 下如果路径里有中文,某些工具读取配置文件时会出编码问题,非常难排查。第二步,打开 IDEA,选择 File -> New -> Project from Existing Sources,定位到项目根目录的 pom.xml,以 Maven 项目方式导入,然后等待依赖下载完成。如果下载很慢,去 maven 的 settings.xml 里配置阿里云镜像,能省一大半时间。第三步,打开资源目录下的 jdbc.properties,把数据库地址、用户名、密码改成你自己的。第四步,在 Navicat 里新建一个数据库,字符集选 utf8mb4,然后执行项目里的 SQL 脚本,导入表结构和初始数据。第五步,配置 Tomcat,在 Deployment 标签页把项目部署上去,Application context 建议填/,这样可以直接通过http://localhost:8080/访问系统首页。

配置文件改完之后,启动前再看一遍控制台有没有红色报错。没有报错但访问 404,先看看 Tomcat 的访问路径和项目上下文路径是否一致。按照这套顺序来,绝大多数项目都能在一小时内跑起来。

4.3 启动失败的快速定位思路

启动失败是这门课里最磨人的环节,但经验多了之后会发现,90% 的问题集中在四个地方。第一,端口被占用。Tomcat 默认端口 8080,如果被某个进程占用,启动日志里会直接写Port 8080 required by Tomcat ... is already in use,解决方式是在 IDEA 的 Run Configuration 里修改 Tomcat 端口,或者在命令行查找占用端口的进程并杀掉。第二,数据库连不上。启动报Communications link failure或者Access denied for user,这种问题优先检查 jdbc.properties 里的主机、端口、用户名、密码有没有填错,然后再看 MySQL 服务是否已经启动。第三,Maven 依赖缺失。如果启动时频繁报ClassNotFoundException,基本可以断定某个依赖没有下载完整或没有被打包进项目,执行一次 Maven 的 clean 和 package,再看 target 目录下是否有对应 jar。第四,日志里有 Spring 或 MyBatis 的配置错误,一般是 XML 配置文件中的路径写错或者 Bean 扫描包路径不匹配,注意检查 Mapper 接口包名和 Mapper XML 文件路径是否对应。

5. 使用过程中的常见坑与排查心得

5.1 数据库连接、乱码、MyBatis 映射问题速查

现象可能原因解决方法
启动报 Communications link failureMySQL 服务未启动,或连接地址端口写错检查 MySQL 服务状态,核对 URL 和端口
登录时 Access denied for user数据库用户名或密码错误修改 jdbc.properties,确认 hosts 权限允许远程登录
页面操作时 Unknown column 报错实体类字段和数据库列名不一致在 MyBatis 配置中开启驼峰映射,或修改字段名对应关系
插入数据后拿不到自增主键没配置 useGeneratedKeys在 insert 语句中配置 useGeneratedKeys="true" keyProperty="id"
页面中文显示问号JSP 页面编码、Tomcat 编码或数据库字符集不对统一使用 UTF-8,Tomcat 配置 URIEncoding="UTF-8"
删除报外键约束失败该药品已被采购或销售记录引用改成逻辑删除(状态字段),或先处理关联明细

这里面最值得展开的是中文乱码问题。SSM 项目的中文乱码通常是三个位置同时出问题:JSP 页面文件本身的编码没有设置为 UTF-8,数据库表和连接 URL 没有指定 utf8mb4,以及 Tomcat 处理请求参数时的编码设置。检查顺序建议是:先看数据库表字符集,再查 jdbc 连接 URL 里的characterEncoding=utf8,最后在 JSP 页面顶部确认pageEncoding="UTF-8"。如果这几个都改了还是乱码,可以给 SpringMVC 配置一个编码过滤器 CharacterEncodingFilter,强制请求和响应都走 UTF-8。

5.2 配置了事务但没生效是怎么回事

@Transactional注解加上去了,但回滚却失效,这个问题我有一次排查了很久。最后发现原因是 Spring 的配置文件里没有开启注解事务管理,也就是缺少<tx:annotation-driven transaction-manager="transactionManager" />或者对应的 Java 配置。没有这一行,@Transactional就是个普通注解,不会触发任何行为。另外还要注意,事务默认只对 RuntimeException 回滚,如果代码里抛出的是 checked Exception,事务不会自动回滚,需要在注解上设置 rollbackFor。课程设计里写业务校验时,建议直接抛出new RuntimeException("库存不足")这类运行时异常,简单直接,也能利用到默认回滚机制。

5.3 MyBatis 写 SQL 的几个隐性习惯

用 MyBatis 写 SQL 时,代码规范比想象中更重要。查询结果映射到实体类时,如果数据库列名是drug_name,实体属性是drugName,必须在 mybatis-config.xml 里开启mapUnderscoreToCamelCase,否则查询结果里这个字段就是 null。复杂的多表查询建议使用 ResultMap 而不是实体类返回,因为列名冲突时实体类根本承载不了。插入数据时如果用了自增主键,记得配置useGeneratedKeys="true"和keyProperty="id",否则插入之后拿不到主键,后续写明细表时会很被动。最后一个习惯是,尽量不要在 Mapper XML 里写SELECT *,特别是联表查询,列名重复会导致字段值覆盖,显式列出需要的列名在排查问题时能明显少折腾。

6. 配套论文文档怎么组织:1 万字并不难凑,难点在结构

6.1 课程设计论文的章节结构与字数分配

标题里说“带论文文档 1 万字以上”,这个字数对药店管理系统来说,只要结构搭起来,完全能写够,而且不难。我的建议是分七章:第一章绪论写选题背景和意义,大概写 800 字;第二章相关技术介绍写 Spring、SpringMVC、MyBatis、MySQL 的基本概念和作用,大概写 1500 字;第三章系统需求分析写功能需求和非功能需求,配上用例图,大概写 1500 字;第四章系统设计写总体架构、功能模块划分、流程图,大概写 1500 字;第五章数据库设计写 E-R 图、表结构说明,大概写 1500 字;第六章系统实现是重头戏,每个模块写页面截图、核心代码、实现思路和主要代码说明,大概写 2500 字以上;第七章系统测试写测试用例、测试过程和结果,大概写 1000 字;最后加摘要、目录、参考文献和致谢。

这样算下来总量超过一万字一点问题没有。关键点在每个章节不要写空话。比如“相关技术介绍”里不要只写“Spring 是一个轻量级框架”,要写你在这个项目里具体用它做了什么——用了 IoC 管理哪些对象、用了 AOP 处理什么事务。每章内容都跟项目实际结合起来,论文才有说服力。

6.2 系统实现章节最容易出现的问题

论文评审老师最反感的就是“代码堆砌式”写法。如果一整页全是代码,没有任何解释,这种论文核心质量很差。正确的做法是:先放一张功能模块运行的界面截图,用一至两段话说明这个界面的功能,再截取该模块中一段有代表性的核心代码,然后用文字解释这段代码的流程和关键点。以药品管理为例,可以讲清楚新增药品时如何处理前端表单数据、Service 层如何校验必填字段、Mapper 层如何执行 insert,以及为什么价格用 decimal 而不是浮点型。一段代码配两段解释,一万字很快就自然写出来了。

另外,截图要统一边框和大小,论文里的图片要有编号和图题,表要有表头。这些格式细节虽然更偏向排版,但在评审时印象分影响很大。先搭好主干,再逐段细化,比自己闷头从第一章开始写高效得多。

6.3 测试章节怎么写才有说服力

测试部分不用写太复杂,重点是展示测试用例的覆盖度。把核心功能分成登录模块、药品管理模块、入库模块、销售模块、库存预警模块,每个模块列出三个左右的测试用例,包括正常操作和异常操作。比如销售模块的测试用例可以设计为:正常销售数量小于等于库存,验证扣减成功;销售数量大于库存,验证系统拒绝并提示库存不足;未登录状态直接访问销售页面,验证请求被拦截。每个用例写清楚测试步骤、预期结果、实际结果,页面上再补一两个操作截图,这个章节就非常完整了。

个人经验里有几个东西值得提醒。第一,千万别改完数据库表结构再去运行原来 SQL,最简单的数据恢复方式是保留一份初始脚本,数据库搞坏了直接重导。第二,系统里的时间字段建议所有表统一格式,避免 Oracle 风格和 MySQL 风格的日期写法混在一起,统计报表时非常难受。第三,做演示前一定要把药品库存预警值设置成当前库存附近的数字,这样点击首页就能看到“药品库存不足”的红色告警,演示效果比翻半天列表强太多了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询