SSM药品售卖系统实战:从环境搭建到下单库存扣减的完整链路

简介:本资源是一套基于SSM框架实现的网上药品售卖系统完整项目包,面向计算机相关专业学生、Java初学者及需要完成毕业设计或课程设计的人群,帮助解决从零搭建电商类Web应用时缺少完整参考案例的问题。压缩包共19.19MB,包含项目源码、数据库脚本、答辩PPT与毕业论文等文件,源码可直接导入IDEA或Eclipse运行,数据库采用MySQL,后端基于Spring+SpringMVC+MyBatis+Maven,前端使用JSP、CSS与JS,技术栈贴合主流企业开发。系统分为管理员与用户两种角色:后台涵盖药品分类、药品信息、用户、健康资讯、客服回复、轮播图与订单管理;前台支持注册登录、药品推荐与详情、收藏、购物车、立即购买、评论、健康资讯、个人中心、我的订单、地址、收藏、客服及余额充值等功能。已有89人学习,适合作为毕设参考或SSM入门练手项目。

1. 从一份药品售卖系统源码说起:SSM 到底能扛住什么

很多 Java 学习者手里都攥着一份「网上药品售卖系统」的源码包,SSM 框架、B/S 架构、带数据库脚本和答辩 PPT,看起来该有的都有。但真正打开跑一遍,十有八九卡在环境配置、依赖冲突或者数据库连不上。问题不在于这份源码有多难,而在于大多数人把它当成「下载即用」的成品,忽略了 SSM 项目从环境搭建到业务落地的完整链路。这个标题背后真正要解决的问题是:如何用 Spring + SpringMVC + MyBatis 这套经典组合,搭出一个能跑通药品浏览、下单、库存扣减、后台管理的 B/S 系统,并且理解每一层在干什么。适合正在做课程设计、毕业设计,或者想从零手写一个 SSM 项目来巩固 Java Web 基础的开发者。药品售卖这个场景比通用商城更具体——有药品分类、处方药与非处方药的区分、库存批次管理,这些业务约束会让你的 SSM 实践比增删改查更有含金量。

2. 环境搭建与 SSM 整合:从零把架子立起来

2.1 为什么选 SSM 而不是 Spring Boot

2024 年还在用 SSM 做项目,很多人第一反应是「过时了」。但从学习路径看,SSM 把 Spring 的 IoC、AOP,SpringMVC 的请求映射、参数绑定,MyBatis 的 SQL 映射、动态 SQL 全部暴露在你面前,没有自动配置帮你兜底。药品售卖系统里,药品分类树、订单状态机、库存扣减这些逻辑,用 SSM 写一遍,你对 Java Web 分层架构的理解会扎实得多。常见做法是 JDK 8 + Maven 3.6 + Tomcat 8.5 + MySQL 5.7,这个组合在各类教程和源码包里兼容性最好。如果你用 JDK 17 配 Tomcat 10,javax.servlet 包名变成 jakarta.servlet,老源码直接编译不过,这是第一个翻车点。

2.2 Maven 依赖与版本锁定

SSM 整合最怕版本打架。Spring 5.3.x 配 MyBatis 3.5.x 是经过大量项目验证的组合,别追新。下面是我一般会用的 pom.xml 核心依赖片段:

<!-- Spring 核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.30</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.30</version> </dependency> <!-- MyBatis 与 Spring 整合 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <!-- 连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency>

逻辑说明:spring-webmvc 已经传递依赖了 spring-context 和 spring-web,不需要重复声明。mybatis-spring 是 MyBatis 和 Spring 事务管理的桥梁,版本必须和 MyBatis 主版本匹配。MySQL 驱动 8.x 对应 com.mysql.cj.jdbc.Driver,5.x 对应 com.mysql.jdbc.Driver,写错直接报找不到驱动。Druid 连接池在药品系统里有个实际好处:它的监控面板能看到慢 SQL,库存扣减这种高频操作调优时很有用。

2.3 web.xml 与 Spring 配置文件的职责划分

SSM 项目通常有三个配置文件:applicationContext.xml 管 Service 和 DAO 层,spring-mvc.xml 管 Controller 层,mybatis-config.xml 管 MyBatis 全局设置。web.xml 里要注册 ContextLoaderListener 加载 applicationContext.xml,再注册 DispatcherServlet 加载 spring-mvc.xml。关键点是:spring-mvc.xml 只扫描 @Controller,applicationContext.xml 扫描 @Service 和 @Repository,否则事务注解会失效。这个坑我见过太多次——Controller 里调 Service,事务死活不回滚,就是因为 Service 被 MVC 容器扫描了,而 MVC 容器默认不开启事务代理。

<!-- applicationContext.xml 关键片段 --> <context:component-scan base-package="com.pharmacy"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>

参数说明:exclude-filter 把 @Controller 排除掉,让 Controller 只被 spring-mvc.xml 扫描。transactionManager 的 dataSource 必须和 MyBatis 用的是同一个,否则事务不生效。tx:annotation-driven 开启 @Transactional 注解支持,药品下单扣库存的方法上要加这个注解。

3. 数据库设计与药品业务建模:表结构决定后期改不改得动

3.1 药品表、分类表、订单表的核心字段

药品售卖系统的数据库设计有几个业务特点:药品有批准文号、生产厂家、有效期;处方药和非处方药要区分;库存要支持批次和效期管理。下面是最小可用的表结构:

CREATE TABLE `drug` ( `id` int NOT NULL AUTO_INCREMENT, `drug_name` varchar(100) NOT NULL COMMENT '药品名称', `drug_code` varchar(50) DEFAULT NULL COMMENT '批准文号', `category_id` int NOT NULL COMMENT '分类ID', `price` decimal(10,2) NOT NULL, `stock` int NOT NULL DEFAULT 0, `is_prescription` tinyint(1) DEFAULT 0 COMMENT '0非处方 1处方', `manufacturer` varchar(200) DEFAULT NULL, `expire_date` date DEFAULT NULL, `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category` (`category_id`), KEY `idx_name` (`drug_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

逻辑说明:drug_code 存批准文号,这是药品唯一标识,比自增 ID 更适合做业务查询。is_prescription 用 tinyint 而不是 enum,MyBatis 映射到 Java 的 Integer 或 Boolean 都方便。idx_category 和 idx_name 两个索引,前者支撑分类浏览,后者支撑搜索。utf8mb4 字符集必须显式指定,否则药品名里的特殊字符会乱码。

3.2 订单与库存的关联设计

订单表 order_main 和订单明细 order_detail 是一对多。库存扣减有两种做法:下单即扣,或者支付后扣。药品系统建议下单即扣,因为药品库存通常不多,超卖比少卖更麻烦。扣减时用乐观锁:

UPDATE drug SET stock = stock - #{quantity} WHERE id = #{drugId} AND stock >= #{quantity}

返回影响行数为 0 就说明库存不足,抛异常回滚。这个 SQL 要放在 Service 方法里,和插入订单在同一个 @Transactional 事务中。注意 MySQL 默认隔离级别是 REPEATABLE READ,这个 UPDATE 会加行锁,并发下不会超卖。

3.3 MyBatis 映射文件与动态 SQL

药品列表查询通常带多个筛选条件:分类、价格区间、是否处方药、关键词。用 MyBatis 的<where>和<if>标签拼动态 SQL:

<select id="selectByCondition" resultMap="DrugMap"> SELECT * FROM drug <where> <if test="categoryId != null"> AND category_id = #{categoryId} </if> <if test="keyword != null and keyword != ''"> AND drug_name LIKE CONCAT('%', #{keyword}, '%') </if> <if test="isPrescription != null"> AND is_prescription = #{isPrescription} </if> <if test="minPrice != null"> AND price &gt;= #{minPrice} </if> </where> ORDER BY create_time DESC LIMIT #{offset}, #{limit} </select>

参数说明:<where>标签会自动去掉第一个多余的 AND。&gt;=是 XML 转义,直接写>=在 XML 里会报错。LIMIT 分页参数 offset 和 limit 由 Service 层计算,pageNum 从 1 开始时 offset = (pageNum - 1) * pageSize。这个查询在药品数量上万时,LIKE '%keyword%' 会全表扫描,后期可以换成全文索引或者 Elasticsearch,但课程设计阶段够用。

4. 避坑与排查:SSM 药品系统最常见的五个翻车现场

4.1 启动报 NoClassDefFoundError 或 ClassNotFoundException

现象:Tomcat 启动时控制台刷一堆 NoClassDefFoundError,或者访问接口报 500,日志里 ClassNotFoundException。原因通常是 Maven 依赖没有打包到 WEB-INF/lib 下,或者依赖冲突导致某个类被覆盖。解决:在 IDEA 里检查 Project Structure → Artifacts,确认 lib 目录下有所有依赖 jar。用mvn dependency:tree看有没有重复依赖,比如同时引入了不同版本的 spring-core。药品系统里常见的是 servlet-api 和 jsp-api 被重复引入,这两个应该 scope 设为 provided。

4.2 数据库连接报 Access denied 或 Public Key Retrieval is not allowed

现象:启动时 Druid 初始化失败,报 Access denied for user 'root'@'localhost',或者 MySQL 8 报 Public Key Retrieval is not allowed。原因:前者是密码错或者用户没授权;后者是 MySQL 8 默认用 caching_sha2_password 认证插件,JDBC 连接串需要加参数。解决:连接串改成jdbc:mysql://localhost:3306/pharmacy?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true。serverTimezone 不设的话,datetime 字段存进去会差 8 小时,药品有效期这种日期字段尤其不能错。

4.3 事务不回滚,库存扣了订单没生成

现象:下单接口报异常,但库存已经扣了,订单表没数据。原因:Service 类没有被 Spring 事务代理,或者异常类型不是 RuntimeException,@Transactional 默认只回滚运行时异常。解决:确认 applicationContext.xml 扫描了 Service 包且开启了 tx:annotation-driven。如果抛的是 checked exception,在 @Transactional 里加 rollbackFor = Exception.class。另外,Controller 里 try-catch 把异常吞了,事务也感知不到,别在 Controller 层捕获 Service 的异常。

4.4 中文乱码,药品名存进去变成问号

现象:前端提交的药品名在数据库里显示为 ???。原因:三个地方要统一 UTF-8——数据库连接串加 characterEncoding=utf8,web.xml 里配 CharacterEncodingFilter,Tomcat 的 server.xml 里 Connector 加 URIEncoding="UTF-8"。解决:CharacterEncodingFilter 的 forceEncoding 设为 true,并且要放在所有 Filter 最前面。MySQL 建库时用CREATE DATABASE pharmacy DEFAULT CHARSET utf8mb4,别用默认的 latin1。

4.5 静态资源 404,CSS 和 JS 加载不出来

现象:页面能打开但没样式,F12 看 CSS 和 JS 请求全是 404。原因:DispatcherServlet 在 web.xml 里配了/拦截所有请求,静态资源也被 SpringMVC 接管了,但没有对应的 Handler。解决:在 spring-mvc.xml 里加<mvc:default-servlet-handler/>或者<mvc:resources mapping="/static/**" location="/static/"/>。前者把找不到的请求交回给 Tomcat 默认 Servlet 处理,后者显式映射静态资源目录。药品系统的图片上传目录也要用 mvc:resources 暴露出来,否则上传成功但显示不出来。

5. 从能跑到能答辩:源码之外你还需要补什么

5.1 用 Postman 把核心接口跑一遍

源码能启动只是第一步,答辩时老师大概率会让你演示下单流程。我一般会先用 Postman 把接口按顺序调一遍:登录拿 session → 查询药品列表 → 加入购物车 → 提交订单 → 查看订单详情。这样能快速定位是前端问题还是后端问题。比如下单接口返回 500,看日志是库存扣减 SQL 报错还是订单插入字段超长。药品名 varchar(100) 一般够用,但生产厂家 varchar(200) 如果填了很长的公司全称可能截断,建表时留够余量。

5.2 答辩 PPT 里应该放什么图

答辩 PPT 不是把源码截图堆上去。老师想看的是你的设计思路。建议放四张图:系统架构图(B/S 三层结构,标注 SSM 各层职责)、数据库 ER 图(药品、分类、订单、用户四个核心实体及关系)、核心业务流程图(下单扣库存的时序)、接口测试截图(Postman 调通的证据)。源码包里的 PPT 模板可以参考结构,但业务描述要换成你自己项目的药品场景,别直接抄商城的。

5.3 毕业论文里值得展开的两个技术点

论文别写成功能说明书。两个方向可以写出深度:一是 SSM 整合过程中的事务管理机制,结合库存扣减场景分析 @Transactional 的传播行为和隔离级别;二是 MyBatis 动态 SQL 在药品多条件查询中的应用,对比拼接 SQL 字符串和<where>标签的差异,附上执行计划分析。这两个点既有代码支撑,又能体现你对框架的理解,比罗列「系统实现了增删改查」强得多。

5.4 源码二次开发的入手顺序

拿到一份药品售卖系统源码,别急着改功能。按这个顺序来:先跑通登录,确认数据库连接和事务配置没问题;再跑通药品列表和详情,确认 MyBatis 映射和分页正确;然后跑通下单,确认库存扣减和订单插入在同一个事务里;最后改后台管理,加一个药品批量导入或者库存预警。每跑通一个模块,在代码里加一行注释记录你改了什么、为什么改。这个习惯在答辩时被问到「你做了哪些工作」时,就是你的后悔药。

我自己带过的项目中,凡是能把 SSM 药品系统从环境搭建到下单流程完整讲清楚的人,后面转 Spring Boot 或者看其他框架源码都很快上手。框架会变,但分层思想、事务边界、SQL 映射这些底层逻辑不会变。希望帮到你。

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