基于SSM框架的药房管理系统设计与实现
简介一套基于SSM框架的药房管理系统毕业设计源码面向计算机专业毕业生与需要项目实战的Java学习者。项目获得导师认可评审分达98分所有源码均经过本地编译运行与严格调试可直接部署。系统难度适中涵盖前端页面、后端逻辑与数据库脚本适合作为毕设参考、功能扩展或框架实践基础。资源包为zip压缩包共1222个文件约19.56MB包含Java源码、JSP动态页面、CSS样式、JavaScript脚本、HTML页面、图片资源、XML及properties等配置文件以及SQL数据库脚本结构清晰便于按模块查阅与二次开发。目前已有174人学习或下载说明这套项目源码在毕业设计场景中具有一定参考价值。借助完整包体可快速理解药房管理系统的前后端交互、数据库表设计及SSM框架整合方式减少从零搭建的重复工作便于快速启动项目并扩展功能。1. 基于SSM框架的药房管理系统值得花三天时间跑通并讲透的毕设选题去社区药店买过药都见过那些场景店员在电脑上输一个药名库存、价格、效期立刻显示开单、结账、打印小票一气呵成。毕业设计题目“基于SSM框架的药房管理系统”要交付的正是这样一套能跑的Web系统——用户登录后按角色操作药品档案、供应商、采购入库、销售出库和库存预警后端用Spring管理对象和事务SpringMVC接收页面请求MyBatis把业务数据落到MySQL。附件里的数据库脚本也不是配角它是整个系统的地基。这道题适合两类人来拿。一是需要交毕设的学生功能量刚好卡在“CRUD之上、微服务之下”能讲清楚每一步原理二是学完Java基础想验证自己能不能独立部署项目的人。SSM又是Java后端面试八股文里的常客把这一套跑通比背十道框架题都管用。接下来我按这类项目的常见结构从数据库到登录页把落地路径讲清楚包括那些让你半夜重启Tomcat的坑。2. SSM框架为什么撑得起这个药房系统选型逻辑与模块拆解2.1 SSM三件套的分工以及它为什么是面试常客打开一个SSM项目的pom.xml你会发现依赖其实就那么几个核心模块。Spring负责两件事把Controller、Service、SqlSessionFactory这些对象放进容器统一管理解决到处new对象导致的耦合再用AOP统一切事务、记操作日志让业务代码里不掺这些横切逻辑。SpringMVC则只做一件事把浏览器发来的URL映射到Controller方法再把Controller返回的视图名或JSON交给前端渲染。MyBatis在SQL层给你留了最大灵活性你可以手写多表join、自己控制乐观锁而不是被全自动ORM的映射规则限制住。为什么毕设题目会选SSM而不是Spring Boot一个现实原因是很多高校的毕设题库是前几年沉淀下来的题目名就写着SSM另一个原因是SSM的配置过程繁琐反而逼你接触web.xml、DispatcherServlet、数据源装配这些底层细节。Spring Boot把这些全自动了你反而说不出请求是怎么从URL走到Mapper的。面试时Spring的IoC/AOP、SpringMVC的DispatcherServlet流程、MyBatis的#{}和${}区别都是高频考点亲手配过一遍SSM被追问时能拿出实例讲而不是背概念。这套组合放在药房管理这种“事务多、表关系清晰、并发量低”的场景里性价比很高。药房的业务本质是进销存——采购入库、销售出库、库存变化每一步都涉及多张表的一致性。Spring声明式事务加上MyBatis的灵活SQL刚好能把这个约束写得干净利落。2.2 功能模块怎么拆从店员视角倒推表结构设计药房系统的第一步不是画用例图而是把自己代入真实岗位看他们每天干什么。店员收银时看什么药品的通用名、规格、生产厂家、批准文号、零售价。卖药之前要确认什么库存够不够、效期过期没有。缺货了怎么办采购员发起采购单到货后做入库。晚上下班老板看什么销售流水、毛利、低库存预警。这些场景直接决定了模块边界和表结构。按这个思路药房管理系统通常拆成五个模块组对应关系如下模块核心功能对应主要表关键字段基础档案药品字典、供应商、会员管理drug_info、supplier、memberdrug_infodrug_code、specification、approval_number采购入库采购单创建审核、到货入库purchase_order、purchase_itempurchase_orderorder_no、supplier_id、status销售出库开单、收银、销售明细sale_order、sale_itemsale_itemdrug_id、quantity、price库存管理批次库存、效期预警、盘点drug_stock、stock_logdrug_stockbatch_no、expire_date、stock_qty系统管理用户、角色、操作日志sys_user、sys_role、sys_logsys_userusername、password、role_iddrug_info只管药品的静态档案价格、规格都在这里drug_stock管动态库存同一个药品可能分多个批次各批次效期不同不能混在一行里。sale_order和sale_item是一对多关系一张销售单含多个药品明细这样的拆法让日结报表和毛利统计都有据可查。stock_log是流水表每一次出入库都写一条记录方便对账和排查问题。有的版本还带一个prescription处方表用来记录处方药购买时的处方信息。如果题目里没要求不一定要加答辩时提到“处方药和非处方药区分管理”的设计思路反而是加分项。2.3 角色权限和业务流程怎么串起来权限模型不需要做得太重。sys_user表里存一个role_id登录时把用户对象放进Session再写一个SpringMVC拦截器检查请求路径前缀/admin/**只允许管理员访问/cashier/**只允许收银员/purchase/**只允许采购员。手写拦截器而不是直接上Spring Security是为了你在答辩时能把Session、Filter、拦截器的关系讲清楚——这是评委喜欢追问的点。业务流程有三条主线。第一条采购流采购员登录后新建采购单选择供应商和药品提交后由管理员审核审核通过到货时做入库操作这一个方法里要同时完成三件事——写采购明细状态、增加drug_stock库存、写stock_log流水任何一步失败都要回滚。第二条销售流收银台选药品时实时查库存下单时校验库存充足再扣减同样要保证销售单和库存扣减在一个事务里。第三条预警流查询库存列表时对比drug_stock.stock_qty和drug_info.warn_line低于预警线就在前端标红提醒采购员补货。这三条流程就是答辩时讲业务的主线。模块拆得清楚表结构才能站得住表结构站得住后面数据库脚本导入和系统跑通就顺理成章。3. 数据库脚本是骨架表结构、导入顺序与验证三件事3.1 药房库里的核心表先看清名字再动手拿到项目包我习惯先不看代码把SQL脚本打开从头到尾扫一遍。药房系统脚本里的表名一般很直观看到drug_前缀知道是药品相关purchase_是采购sale_是销售。先看清楚有哪些表、表之间谁引用谁后面导入时心里才有底。一张典型的核心表结构大致长这样CREATE TABLE drug_info ( id INT NOT NULL AUTO_INCREMENT COMMENT 主键, drug_code VARCHAR(32) NOT NULL COMMENT 药品编码, drug_name VARCHAR(128) NOT NULL COMMENT 药品通用名, specification VARCHAR(64) DEFAULT NULL COMMENT 规格如 500mg*24片, unit VARCHAR(16) DEFAULT NULL COMMENT 单位盒/瓶/袋, manufacturer VARCHAR(128) DEFAULT NULL COMMENT 生产厂家, approval_number VARCHAR(64) DEFAULT NULL COMMENT 批准文号, retail_price DECIMAL(10,2) NOT NULL COMMENT 零售价, warn_line INT DEFAULT 10 COMMENT 库存预警线, status TINYINT DEFAULT 1 COMMENT 1上架 0停用, PRIMARY KEY (id), UNIQUE KEY uk_drug_code (drug_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品档案表;这段建表SQL里值得注意三个点。drug_code建了唯一索引业务上作为药品的“身份证号”导入数据时如果重复会直接报错这是脚本自带的约束检查。retail_price用DECIMAL(10,2)而不是FLOAT金额字段浮点运算会出精度问题这是老工程师的基本功。warn_line是预警线默认为10意思是库存少于10盒就在界面标红。字符集统一用utf8mb4才能存得下生僻字和特殊符号。库存表的设计也得看一眼。drug_stock通常用drug_id batch_no expire_date作为业务唯一键同一药品不同批次的库存分开记录效期管理才做得起来。如果只在一个字段里记总数那效期预警功能就无从谈起答辩时容易被问住。3.2 按依赖顺序导入先建库再建表导入脚本前先确认两件事脚本头部有没有CREATE DATABASE语句以及有没有DROP TABLE IF EXISTS。大多数毕设脚本会在开头先删旧表再建新表方便重复导入少部分脚本只包含建表和INSERT数据需要你手动建库。两种方式按对应场景处理# 方式一脚本自带建库语句直接重定向导入 mysql -u root -p pharmacy.sql # 方式二脚本不含建库语句先手动建库再source mysql -u root -p -e CREATE DATABASE IF NOT EXISTS pharmacy DEFAULT CHARACTER SET utf8mb4; mysql -u root -p pharmacy pharmacy.sql两种方式的区别在数据库连接目标上。方式一不指定库名因为脚本内部已经写了USE pharmacy;方式二先建库再把pharmacy.sql导入到名为pharmacy的库里。如果你在方式二里忘了指定库名MySQL会报“No database selected”的错误导入直接失败这是新手最容易踩的第一道坎。还有一个小技巧如果脚本里的表存在外键依赖导入时担心顺序问题可以先在会话里执行SET FOREIGN_KEY_CHECKS0;临时关闭外键检查导入完再执行SET FOREIGN_KEY_CHECKS1;恢复。常见的毕设脚本里表不多一般不会出这个问题但这一手能应付表结构更复杂的扩展版本。3.3 用几条SQL验证导入是否完整导入成功不代表导入完整。我见过不少同学脚本刷到一半报错终端提示红字一闪而过没在意后面项目启动时各种字段找不到。落地到验证这一步至少跑四条SQL确认数据骨架是完整的-- 1. 确认表都建出来了数量对得上 SHOW TABLES; -- 2. 确认药品档案有数据不是空库 SELECT COUNT(*) AS drug_cnt FROM drug_info; -- 3. 确认库存表和销售明细表都可读字段没导丢 SELECT id, drug_id, batch_no, stock_qty, expire_date FROM drug_stock LIMIT 5; -- 4. 确认管理员账号在密码字段没被截断 SELECT id, username, password, role_id FROM sys_user;SHOW TABLES对照脚本里的建表语句数一遍缺一张表说明导出或导入过程中断了。COUNT(*)药品数量通常应该在几十条以上如果脚本里带测试数据数量太少说明INSERT被跳过。最后查sys_user是为了确认登录入口能进去密码字段如果显示成e10adc3949ba59abbe56e057f20f883e这类32位字符串说明是MD5加密后的值默认密码一般是123456。验证通过之后建议你用IDEA或Navicat的“导出数据库脚本”功能把这个库重新导出一份留作备份。自己导出的脚本是干净的、确定能用的后续如果改坏了数据随时能恢复到初始状态。这份自制脚本也可以作为答辩时“数据库设计”环节的交付物。4. 本地跑通从解压到登录页的最小命令与配置4.1 环境版本怎么搭配才不会翻车SSM是在Maven生态成熟期流行的框架组合版本匹配有个“别太新”的原则。我见过有人拿JDK 17配Spring 4启动直接抛IllegalArgumentException折腾半天发现是版本兼容问题。以下是实测下来最稳的搭配组合组件推荐版本说明JDK1.8SSM老项目的基石大多数毕设源码基于JDK 8编译Maven3.6.33.8以上对镜像配置要求更高没必要冒险Tomcat8.5.x兼容javax.servlet规范Spring 4/5都能跑MySQL5.7 或 8.05.7最省心8.0要配对应驱动mysql-connector-java5.1.49 或 8.0.30版本必须和MySQL大版本对应否则认证报错IDEA2020 之后均可主要是帮你管理Tomcat和MavenTomcat为什么推荐8.5而不是9或10因为Spring 4.x时代构建的SSM项目用的还是javax.servlet包名Tomcat 10把包名改成了jakarta.servlet老war包部署上去会出现ClassNotFoundException。除非你确定源码里Spring版本是5.x且做了适配否则不要轻易上Tomcat 10。这个坑用一句话总结就是Java Web老项目的版本最怕“太新”。4.2 修改配置文件四个必改项解压项目后第一步是找到src/main/resources目录下的数据库连接配置文件。文件名可能是db.properties、jdbc.properties或application.properties内容大同小异。把数据库地址、用户名、密码改成你本机的配置jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/pharmacy?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue jdbc.usernameroot jdbc.password123456连接串里的每个参数都不是装饰。useUnicodetruecharacterEncodingutf8保证中文读写不乱码少了这两个参数药品名字段在页面上大概率显示成问号serverTimezoneAsia/Shanghai解决MySQL驱动在连接时报时区错误的问题allowPublicKeyRetrievaltrue是给MySQL 8用的不加可能报Public Key Retrieval is not allowed。驱动类名也要注意MySQL 8对应com.mysql.cj.jdbc.DriverMySQL 5.7及以下则是com.mysql.jdbc.Driver写反了启动直接报ClassNotFound。第二步改Spring的配置装配文件名字一般是applicationContext.xml或spring-mybatis.xml。重点检查MyBatis的Mapper扫描路径bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.pharmacy.entity/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.pharmacy.dao/ /beanmapperLocations指向XML映射文件的位置常见有两种组织方式一是放在src/main/resources/mapper下写成classpath:mapper/*.xml二是直接放在Java包的dao目录下这时要写成classpath*:com/pharmacy/dao/*.xml。注意classpath和classpath*的区别前者只扫第一个匹配路径后者扫所有Jar包里的类路径写错位置最常见的报错就是Invalid bound statement (not found)。4.3 Maven编译、打包、部署三步走配置改完先别急着启动Tomcat。在项目根目录执行Maven命令编译打包一套走完# 第一步清理并打包跳过单元测试 mvn clean package -DskipTests # 第二步确认target目录下生成了war包 ls -lh target/*.war # 第三步把war包复制到Tomcat的webapps目录 cp target/pharmacy.war $CATALINA_HOME/webapps/ # 第四步启动Tomcat前台模式方便看日志 $CATALINA_HOME/bin/catalina.sh run-DskipTests是跳过测试代码不少毕设项目里测试类依赖了数据库环境不跳过会导致打包失败。catalina.sh run用前台模式启动日志直接打在终端上启动过程如果有异常你第一时间就能看到比startup.sh后台启动完再翻日志方便得多。Windows环境下对应的是catalina.bat run。如果你用的是IDEA更省事的方式是配置Tomcat Server的Artifact直接把war包交给IDEA部署点Debug按钮就能启动。但底层逻辑和命令行没有区别——IDEA只是帮你执行了复制war包和启动Tomcat这两步。4.4 从登录页到完成一次售药自测路径部署成功后访问http://localhost:8080/pharmacy/login能看到登录页说明项目已经跑起来了。系统管理员的账号密码一般初始化为admin / 123456脚本里把密码的MD5值写死在了sys_user表中。登录后按这条路径自测一遍基本能确认全链路可用打开药品字典看药品列表能否正常加载图片和中文是否乱码新增一个测试药品填完保存再刷新列表确认能查到进采购入库选刚才那个药品做一个入库单库存数量增加到收银台模拟售药选择药品、填写数量确认库存扣减生效回到库存列表看库存是否比售药前减少操作日志有没有记录。这五步走完说明从SpringMVC到MyBatis再到MySQL的主链路全部通了。如果哪一步卡住回到日志看异常堆栈绝大多数问题都出在数据库连接和MyBatis映射这两个环节这正是下一章要重点梳理的。5. 常见问题与排查SSM药房系统跑不起来的5个坑5.1 启动报Communications link failureMySQL连接没谈拢现象Tomcat启动日志里出现Communications link failure或Connection refused应用直接启动失败。原因最多的情况是MySQL服务没启动其次是密码不对最后是连接串里的端口写错。SSM项目启动时Spring会初始化数据源并试着获取连接连接失败就会把这个异常抛出来。解决先用命令行确认MySQL本身能连mysql -u root -p能正常进入说明服务没问题再看db.properties里的密码、端口是否匹配。还有一个隐藏问题MySQL 8的默认认证插件是caching_sha2_password如果驱动还停留在5.1.x也会报认证错误换8.0.x的驱动并按上一章配置allowPublicKeyRetrievaltrue即可。5.2 404欢迎页打不开上下文路径和部署目录对不上现象Tomcat启动正常但访问http://localhost:8080/是404访问http://localhost:8080/pharmacy/login也一样。原因一种是war包部署后没有生成对应的解压目录另一种是浏览器访问的URL少了项目上下文路径。Tomcat默认把war包解压到webapps下同名的目录war包叫pharmacy.war上下文路径就是/pharmacy不带这个前缀必然404。解决先看webapps目录下有没有pharmacy这个文件夹没有说明war包没部署成功。再看启动日志里有没有Deploying web application archive和has finished字样。浏览器访问时带上上下文路径http://localhost:8080/pharmacy/很多项目的欢迎页就挂在/login或/index上。5.3 Invalid bound statementMyBatis映射文件没被扫到现象页面操作触发查询时报Invalid bound statement (not found): com.pharmacy.dao.DrugDao.selectList而数据库连接完全正常。原因Mapper接口的Java方法没有在XML映射文件里找到对应的select或insert。绝大多数情况是mapperLocations配置的路径和XML文件实际位置不匹配编译后的XML没有进target/classes所以运行时扫不到。解决先检查编译输出目录比如target/classes/mapper下有没有对应的XML文件没有的话说明Maven没有把XML当资源编译进去在pom.xml的buildresources里加上src/main/java下的XML包含规则。再看XML文件的namespace是否指向了正确的Mapper接口方法id和接口方法名是否一致。这三处对齐问题就解决了。5.4 中文乱码数据库、JSP、Tomcat三处要统一UTF-8现象登录后药品名称显示成???或者页面上的中文全是乱码。原因乱码问题很少是单点原因通常是传递链路里某一环字符集不一致。数据库建表不是utf8mb4、JDBC连接串没加characterEncodingutf8、JSP页面没加pageEncodingUTF-8、Tomcat对URL参数没设URIEncoding这四处只要有一处不一致中文就会在某个环节变形。解决先改数据库连接串按第4章的配置补全参数再检查JSP页面头部是否声明了pageEncodingUTF-8最后在Tomcat的server.xml里给Connector加上URIEncodingUTF-8。三处统一后重建数据乱码基本能解决。注意已经乱码进库的数据需要清掉重导改配置不会自动修复旧数据。5.5 MySQL 8连不上的玄学驱动版本和认证插件现象MySQL客户端能正常连接驱动也换成了com.mysql.cj.jdbc.Driver但项目启动还是报Unable to load authentication plugin caching_sha2_password。原因MySQL 8默认创建用户用的是caching_sha2_password认证插件老版驱动不认识这个插件。这个问题在毕设环境里很常见因为很多人本机装的就是MySQL 8而项目自带的驱动是5.1.x。解决把pom.xml里的mysql-connector-java版本升到8.0.x同时驱动类名改成com.mysql.cj.jdbc.Driver连接串加上allowPublicKeyRetrievaltrueuseSSLfalse。如果你不想动版本也可以登录MySQL执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码;把用户认证插件改回老协议。两条路任选其一项目就能连上了。6. 从能跑到高分答辩前的优化与验证思路6.1 扣库存的事务与乐观更新并发卖同一盒药怎么防超卖药房系统看起来简单评委最爱追问的一个问题是“两个人同时买最后一盒药怎么办”。很多同学写销售逻辑时先SELECT stock_qty判断库存大于0再UPDATE减库存这个流程在并发下会出问题——两次查询都发现库存为1然后各自扣减库存变成负数。解决思路是用一条带条件的UPDATE让数据库来保证原子性Transactional(rollbackFor Exception.class) public void createSaleOrder(SaleOrder order, ListSaleItem items) { for (SaleItem item : items) { int rows stockMapper.deductStock(item.getDrugId(), item.getQuantity()); if (rows 0) { throw new RuntimeException(库存不足销售单已回滚); } } saleOrderMapper.insert(order); saleOrderMapper.insertItems(items); }UPDATE drug_stock SET stock_qty stock_qty - #{qty} WHERE drug_id #{drugId} AND stock_qty #{qty}deductStock返回的条数只有两种情况更新成功返回1库存不足或记录不存在返回0利用数据库行锁天然解决了并发扣减问题。Transactional保证销售单和库存扣减在同一个事务里任何一个明细失败整个销售单撤销不会出现卖了药库存没减的情况。这一小段代码能讲出事务、并发、SQL优化三个层面的深度是答辩的实打实得分点。6.2 列表页提速三件事分页、缓存与懒加载药品一多列表页会明显变慢。最基础的手段是分页用PageHelper插件一行PageHelper.startPage(pageNum, pageSize);接在查询方法前MyBatis会自动拼接LIMIT语句页面上同时拿到总数和当前页数据这是列表页优化的第一档。再往上可以做热点数据缓存把药品字典这类改动少、读得勤的数据放进缓存用Redis还是本地缓存看环境条件能够在答辩时说清楚缓存更新策略就够了——更新药品时同步清掉对应缓存防止脏读。图片、大字段用懒加载列表页先不查明细点开详情再加载这三个点足够撑起“系统优化”的章节。6.3 演示脚本五分钟把业务闭环讲给评委听答辩演示最忌只展示CRUD。一条完整的业务闭环比十个独立页面都管用按这个顺序走步骤操作让评委看什么1管理员登录登录校验和权限跳转2新建一个测试药品药品档案字段校验3采购入库该药品库存增加、流水记录产生4收银台销售该药品库存扣减、销售单生成5回到库存列表库存减少、预警标红的触发逻辑每一步之间都要把因果关系讲出来采购导致库存增加销售导致库存减少库存变化产生流水流水是后续报表的数据来源。这套逻辑讲通评委看到的不只是代码而是你对业务的理解。我写代码有个习惯项目跑通交付之前一定把数据库重新导入一遍密码初始化成文档里的默认值确保任何一台机器按着操作说明都能从零跑起来。当年有同学答辩时用的还是指导老师机器上的旧数据演示完老师一问“本地怎么部署”当场卡壳。跑通只是起点别人能复现才是交付标准。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →