SSM中医方剂管理系统毕设实战:从表结构到代码实现全解析
1. 项目价值与整体拆解思路1.1 为什么毕业设计选了SSM而不是只选Spring Boot“SSM中医方剂管理系统”这串关键词在计算机类毕业设计选题里出现频率极高。我前前后后帮人跑过不少类似的代码也看过各种命名风格的版本这套东西本质上是同一套逻辑用Spring管理业务对象用SpringMVC接收页面请求用MyBatis操作数据库再把中药方剂的增删改查、药材关联、用户登录这些典型业务串起来。很多同学会有疑问现在已经到处都是Spring Boot了为什么还要选SSM从做项目的实际体验来说SSM对理解Web开发的分层逻辑更友好。Spring Boot把大量配置自动完成了初学者往往写完了接口也不知道请求是怎么进来的、拦截器注册在哪儿、事务为什么会生效。而SSM逼着你把web.xml、Spring配置文件、SpringMVC配置文件、MyBatis映射文件都手动过一遍这个过程虽然麻烦但正是毕业设计答辩时要讲的“技术重点”。评审老师问你“登录拦截怎么做的”“事务怎么控制的”你能直接说出配置文件里的标签和Bean的名字这比笼统地说“用了Spring Boot”要有说服力得多。再一个现实原因很多高校的Java课程和实训教材至今仍然以SSM为教学主线项目源码拿到手之后你不需要额外学一套新的注解式开发习惯照着课程里的分层结构就能看明白。所以如果你手里正好是这个题目不用觉得它“老”把SSM吃透后续再转Spring Boot就是一天之内的事。1.2 中医方剂管理系统的功能边界与适合人群先把这个系统的功能边界说清楚。它不是一个复杂的医疗业务系统而是一个典型的管理后台核心服务对象是“方剂”这一条主线。系统里大概包括以下几类功能用户登录与角色区分普通用户能浏览查询管理员还能维护药材、方剂、分类等基础数据。药材信息管理维护中药的编号、名称、类别、描述等信息。方剂信息管理创建方剂时给方剂选择一个或多个药材录入每味药材的用量和备注。方剂查询按方剂名称、药材关键字、分类等条件做模糊检索。分类统计按方剂分类或药材分类统计数量用图表展示这是答辩时比较加分的展示点。这个系统适合两类人。第一类是正在做毕业设计的学生尤其是手头已经有源码但不知道怎么把它跑起来、改明白的人第二类是刚开始学Java Web想找一个小型完整项目练手的人。因为它的业务模型足够简单但覆盖了登录、权限拦截、关联表、事务、分页、模糊查询这些最通用的知识点你能从一个项目里看到一类管理系统的写法。1.3 技术栈解析SSM里的三个组件各自干什么经常有人把SSM当成一个整体去背但拆开看其实分工很明确Spring作为一个大容器负责管理Service、Mapper这些对象的创建和依赖注入。事务也由它统一控制比如保存一个方剂时同时往主表和关联表插入数据要么都成功要么都回滚。SpringMVC负责前端页面请求的分发。用户的请求到达DispatcherServlet之后由它匹配到对应的Controller方法Controller处理完返回视图名称再由视图解析器渲染成JSP页面。MyBatis负责操作数据库。你在Mapper接口里定义方法在XML文件里写SQLMyBatis帮你完成参数映射和结果集映射。这个结构用一句话概括Spring管对象SpringMVC管请求MyBatis管SQL。三件事各干各的互相通过配置串联起来这也是SSM项目调试时定位问题最快的方式——页面没出来先看SpringMVC的映射数据没查到先看Mapper XML里的SQL对象拿不到先看Spring的Bean配置。2. 数据库设计与核心表结构拆解2.1 方剂领域的数据建模要先想清哪四张核心表你说你拿到了一套SSM中医方剂系统的源码第一件事别急着启动先打开SQL脚本看看表结构。因为整个系统的骨架全在表设计里。通常这套系统至少会有四张核心表表名作用关键字段sys_user系统用户表用户名、密码、角色、创建时间category分类表分类名称、父级ID、备注herb药材表药材名称、分类ID、功效描述、状态formula方剂表方剂名称、分类ID、来源方书、主治描述、创建人formula_herb方剂药材关联表方剂ID、药材ID、用量、序号这里要特别说明一下为什么需要“方剂药材关联表”。一个方剂里有多种药材一味药材也可以出现在多个方剂中这是典型的多对多关系。如果只在方剂表里放一个字段存药材名称后续要根据药材反查方剂就会非常痛苦。所以正解是拆出一张中间关联表方剂和药材都只管自己的ID中间的对应关系、每味药的用量、入方顺序都放在关联表里。这个设计在答辩时是必考的点。老师很可能问“为什么这么拆表直接一个字段存储药材名称不行吗”你要回答为了数据规范性和查询效率。拆表之后查询某个方剂用了哪些药材一条关联查询就出来了统计某个药材出现在多少方剂里也不需要解析字符串直接对关联表做GROUP BY计数。2.2 关联表的字段设计剂量、序号这些细节别漏关联表的字段别只放两个ID那样太简陋了。以实际的方剂业务来说至少还要补充三个字段dosage用量比如多少克。同一个药材在不同方剂里的用量是不同的这个信息必须存在关联表里而不是药材主表里。sort_no药材在方剂中的顺序。中药方剂讲究“君臣佐使”药材顺序是有含义的。如果用数组顺序隐含在插入顺序里后面修改时会出错不如显式加一个排序号。remark备注比如炮制方法、特殊煎法。数据库初始化时还要注意编码问题。创建表时建议统一使用utf8mb4防止生僻字和特殊符号乱码。主键类型用INT自增还是用雪花ID毕业设计项目用INT自增就够简单清晰方便手动调试数据。这里有个小经验导入别人的SQL脚本时经常遇到脚本里包含drop database或者drop table语句。如果你本机有其他重要库执行前一定要看一眼最好用一个新的空白数据库来导入这套脚本别直接拿root账号在生产库上跑。2.3 从表结构反推业务查询、统计、权限都藏在SQL里拿到表之后你可以站在作者的角度反推他能做出哪些功能用户表里有role字段就可以做登录后的角色判断管理员看到“新增方剂”按钮普通用户看不到。药材表里有category_id就可以做分类筛选点击某个分类只看该分类下的药材。方剂表和关联表通过formula_id连接天然支持“查询某个方剂的中药组成”。分类表如果设计了pid字段就能做两级分类但多数毕设里分类只有一级不要过度设计。统计功能通常单独写几条聚合SQL。比如统计每个分类下的方剂数量用到的就是GROUP BY加COUNT。数据接口返回JSON前端图表组件拿到后渲染成柱状图或饼图。这些SQL语句是答辩时展示基本功的好素材建议你提前把每条SQL拿出来搞清楚它查的是哪些表用了什么连接方式。3. 核心功能与关键代码实现3.1 Controller-Service-Mapper三层结构与包命名规范SSM项目最讲究分层。我建议源码拿到手先看一眼包结构不同版本命名可能有差异但大方向应该一致com.xxx.system.controller # 控制层接收前端请求 com.xxx.system.service # 业务层处理核心逻辑 com.xxx.system.dao # 数据访问层Mapper接口 com.xxx.system.entity # 实体类 com.xxx.system.interceptor # 拦截器 com.xxx.system.config # 配置类或工具类模块写清楚之后Controller层只做参数接收和结果返回Service层做校验和业务组装Mapper层只写数据库操作。这一层划分不是形式主义它决定着你调试问题时的排查路径。比如查询方剂列表数据不对你只需要在Controller打断点看Service收到的参数是否正确如果参数没问题再进Mapper看SQL是否拼对了条件。每一层各管一段问题会被快速圈定。我在实际看代码时发现一个常见的坏习惯有些人把业务查询逻辑直接写在Controller里一个方法上百行Service层形同虚设。这样的代码能跑但答辩时很难讲清楚。建议你如果拿到的是这种结构花时间把查询逻辑挪到Service里哪怕不改变功能代码层次也会干净很多。3.2 登录逻辑与SpringMVC拦截器配置登录是所有后台系统的入口。很多源码里登录逻辑写得很简单从数据库查用户名密码匹配上了把用户对象塞进Session。核心代码类似这样Controller RequestMapping(/admin) public class LoginController { Autowired private SysUserService sysUserService; RequestMapping(/login) public String login(String username, String password, HttpSession session, Model model) { SysUser user sysUserService.login(username, password); if (user ! null) { session.setAttribute(loginUser, user); return redirect:/admin/index; } model.addAttribute(error, 用户名或密码错误); return login; } }登录之后的保护措施——拦截器——才是更容易被忽略的重点。不用拦截器的话每个页面都要手动判断Session里有没有用户代码重复而且容易漏。SpringMVC拦截器配置一次即可public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); if (session.getAttribute(loginUser) ! null) { return true; } response.sendRedirect(request.getContextPath() /login); return false; } }然后在SpringMVC配置文件中注册这个拦截器给出拦截路径和放行路径mvc:interceptors mvc:interceptor mvc:mapping path/admin/**/ mvc:exclude-mapping path/admin/login/ mvc:exclude-mapping path/static/**/ bean classcom.xxx.system.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors注意一个细节静态资源路径一定要排除掉否则JSP页面里的CSS和JS都会被拦下来页面样式全丢。这个问题在答辩前一刻出现会非常致命提前加上排除路径能省掉很多麻烦。3.3 保存方剂时的多药材选中处理与事务保证创建方剂页面里最常见的是一个药材多选框很多JSP自带源码用的是checkbox多选。提交时前端会传一个String数组SpringMVC能直接绑定到Controller方法的参数上RequestMapping(/save) ResponseBody public Result save(Formula formula, Long[] herbIds, String[] dosages) { formulaService.saveFormula(formula, herbIds, dosages); return Result.success(); }Service里做的事情有两步第一步往formula主表插入方剂基本信息生成方剂ID第二步循环herbIds数组往formula_herb关联表逐条插入药材ID和对应的用量。这里必须加事务因为两步是一个整体如果第二步中途失败而第一步没回滚就会留下一个没有药材的“空方剂”。事务注解放在Service实现类的方法上这是SSM里最标准的做法Transactional public void saveFormula(Formula formula, Long[] herbIds, String[] dosages) { formulaMapper.insert(formula); for (int i 0; i herbIds.length; i) { formulaHerbMapper.insert(formula.getId(), herbIds[i], dosages[i], i 1); } }这里有一个容易踩的坑Spring的事务默认只在运行时异常时回滚如果Controller层把异常catch掉了事务就无法触发回滚。所以在开发阶段建议不要吞异常哪怕控制台打出一堆错误信息也比数据写坏要好。3.4 动态条件查询和分页LIKE、#{}、PageHelper方剂查询功能一般允许按方剂名称、主治关键词、分类等条件组合筛选。如果为每种组合写一个查询方法会产生大量重复代码。MyBatis的动态SQL是最好的解药在Mapper XML里用if标签拼接条件select idselectByCondition parameterTypecom.xxx.system.entity.Formula resultTypecom.xxx.system.entity.Formula select * from formula where if testname ! null and name ! and name like concat(%, #{name}, %) /if if testcategoryId ! null and category_id #{categoryId} /if /where order by create_time desc /select两个关键点你必须知道。第一动态SQL里写参数占位符要用#{}而不是${}${}会直接拼字符串存在SQL注入风险#{}会预编译成占位符才是最安全的写法。第二LIKE语句不要直接写 like %#{name}%这样参数绑定时会报错正确写法是用concat函数拼接百分号。分页功能通常是项目里引入PageHelper插件来实现。使用起来很简单PageHelper.startPage(pageNum, pageSize); ListFormula list formulaMapper.selectByCondition(formula); PageInfoFormula pageInfo new PageInfo(list);注意PageHelper使用的一个坑startPage必须紧跟在要分页的Mapper查询之前中间如果插入其他查询分页就会作用到错误的SQL上。有同学反映“分页不生效”“每页数量不对”绝大多数是这个问题。3.5 统计接口与ECharts图表展示统计图表是最容易出效果的模块。后端写一个接口执行聚合查询返回JSON数据前端用ECharts渲染成柱状图或饼图。统计方剂分类数量的Mapper类似这样select idcountByCategory resultTypemap select c.name as name, count(f.id) as value from formula f left join category c on f.category_id c.id group by f.category_id /select前端页面上引入ECharts的JS文件再用Ajax请求这个接口拿到数据后塞进ECharts的series配置里即可。很多入门同学把这块当成难点其实原理不复杂后端只负责出数据前端只负责画图两者通过JSON接口交互。答辩时建议把图表放在演示的中段或后段。先演示常规的增删改查再切到统计页让老师看到你除了基础CRUD之外还考虑了数据展示这往往是拿高分的加分项。4. 源码导入到本地正常运行的完整流程4.1 环境准备清单JDK、MySQL、Maven、Tomcat选哪个版本拿到源码之后不要急着用IDEA直接打开。先把环境准备好版本选对能减少一半的启动问题。这套SSM项目常见组合如下工具推荐版本说明JDK1.8兼容性最好绝大多数SSM老项目的默认配置都是1.8MySQL5.7或8.0两者都行但SQL脚本是5.7语法时在8.0要注意时区问题Maven3.6.x编译打包用版本过高偶尔会有插件兼容问题Tomcat8.5或9.0对应JDK1.8完全够用IDEA任意较新版本社区版或专业版都可只要能支持Maven和Tomcat插件如果你本机已经装了更高版本的JDK比如JDK17运行SSM项目时很可能出现Spring版本不兼容、Tomcat启动失败的情况。经验之谈专门为这个项目装一个JDK1.8并通过IDEA里的Project Structure指定项目的SDK为1.8是最保险的做法。4.2 导入源码、配置数据库连接和Maven依赖源码解压后在IDEA里选择File - Open定位到pom.xml所在目录让IDEA以Maven项目方式打开。首次打开会花不少时间下载依赖Maven仓库如果下载慢或者一直失败可以检查一下本机Maven的settings.xml是否配置了国内镜像。配置很简单在mirrors节点里加一个镜像地址就行不要用默认中央仓库速度差别很大。依赖下载完成后先修改数据库配置。最常见的配置文件是src/main/resources下的jdbc.propertiesjdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/fangji_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456注意两个问题。第一MySQL 8.0的驱动类名变成了com.mysql.cj.jdbc.Driver如果源码里写的是老驱动名需要改成新的否则启动时连接数据库就会报ClassNotFoundException。第二url里一定要带characterEncodingutf8否则JSP页面显示中文会变成问号。数据库连接信息改完之后还要确认本地MySQL已经创建了对应的库并导入了SQL脚本。4.3 发布到Tomcat并初始化默认账号IDEA里运行SSM项目的方式有几种最通用的是配置Tomcat。在Run/Debug Configurations里新建一个Tomcat Server - Local然后选择Deployment选项卡把项目的war包加进去。如果项目结构本身是war包打包方式IDEA会自动识别并生成Artifact。配置好之后直接启动Tomcat控制台输出“started in xxxx ms”就说明启动了。启动完成访问首页比如http://localhost:8080/fangji/login项目名那一级路径是由部署时的Application context决定的通常在Tomcat配置里能看到。如果页面出来之后登录不上先用默认账号密码试很多源码自带admin/admin123之类的初始化账号进不去就看数据库里user表里到底有什么账号。密码字段如果存的是加密串不能用明文登录要确认加密方式再注册测试账号。4.4 部署时常见的环境还原关键注意这几个环节是反复踩坑的重灾区我通常按此顺序检查确认MySQL服务启动且脚本确实完整执行过。判断方法很简单在Navicat或命令行里select count(*) from formula能看到数据就说明导入成功。确认jdbc.properties里的密码与本地MySQL一致。有些同学改了数据库密码配置里还是demo资料里的旧密码连接失败是必然的。确认Tomcat配置里没有端口冲突。如果8080被占启动时会立即报错。确认项目编码。IDEA右下角把文件编码改成UTF-8再查看IDEA的vmoptions里是否加了-Dfile.encodingUTF-8在Windows下不设置的话容易乱码。5. 项目实操中的常见问题与排查记录5.1 静态资源被拦截导致CSS和JS加载不了页面能打开但完全没有样式这是SSM项目最经典的问题之一。原因基本可以锁定在SpringMVC的拦截器把静态资源拦截了或者是SpringMVC前端控制器DispatcherServlet把静态资源路径也当成了请求接口。SpringMVC配置里需要放开静态资源映射mvc:default-servlet-handler/ mvc:resources mapping/static/** location/static//同时如果你在SpringMVC配置文件里注册了登录拦截器必须把/static/、/js/、/css/**、/images/**这些路径排除掉。排除的方式前面已经写过了在exclude-mapping里一条条加或者直接在拦截器路径上只拦截需要保护的/admin/**目录。5.2 中文乱码不好用排除法定位到Tomcat、连接、页面三层乱码问题要分清楚是哪种乱码。数据库里显示的是乱码还是浏览器页面乱码还是URL参数乱码处理方式不一样。数据库乱码检查表字符集和连接url。表默认字符集改成utf8mb4url带characterEncodingutf8就行。页面乱码检查JSP文件开头是不是有% page contentTypetext/html;charsetUTF-8 %没有的话补上。请求参数乱码在web.xml里配置Spring自带的CharacterEncodingFilter强制请求和响应都走UTF-8。filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping这个filter要放在过滤器链的最前面否则后面其他过滤器先处理了请求编码已经变了再设置就晚了。5.3 Mapper XML提示绑定失败或Mapper接口报错启动项目时出现类似“Invalid bound statement (not found)”的报错说明MyBatis没有找到Mapper接口对应的XML文件。最常见的原因是resources目录下少放了XML文件或者XML里的namespace没有写Mapper接口的全限定名。检查思路Mapper接口的完整类名比如com.xxx.system.dao.FormulaMapper。XML文件中的namespace必须和它完全一致。XML文件里每个select/insert的id必须和Mapper接口里的方法名一致。如果XML放在src/main/java的包下面默认Maven打包时不会把XML文件带进classes目录。这时要么把XML放在src/main/resources里并保持路径一致要么在pom.xml里把XML目录加入构建资源。这个问题是有源码的同学特别容易遇到的因为操作IDEA导入时目录结构看起来是对的但编译后XML没打包进去导致运行时找不到SQL。5.4 启动太慢、端口占用、数据库连不上怎么一步步找启动太慢先看Maven依赖是不是每次都在下载。把本地仓库路径设置好依赖下载完成后尽量使用依赖缓存不要每次clean都重新全量下载。端口占用处理方式很直接。命令行里执行netstat -ano | findstr 8080查出来占用8080的进程PID再配合任务管理器把它结束掉。如果不想结束进程改Tomcat端口也行server.xml里把8080改成8081、8082都可以但要记得访问地址也同步改。数据库连不上要分情况。最常见是驱动类没加载其次是账号密码不对再就是MySQL服务没启动。你在IDEA里看控制台的完整堆栈信息里面会直接写出错误原因别只看第一行提示往下翻几行定位到Caused by真正的病因都在那里。5.5 答辩时关于源码的几条保命提醒这个内容放在最后再说几句。手里有源码的同学光能把项目跑起来还不够毕业设计答辩更看重“是不是自己做过”。建议你把核心代码逐行看一遍先搞懂登录和拦截器再看方剂保存的事务逻辑最后看统计SQL。我给朋友们讲过很多次答辩老师常问的问题翻来覆去就那几个登录怎么做到权限控制的、多对多关系怎么处理的、分页是怎么实现的、事务加在哪里。如果时间来得及自己动手改掉项目里的一个小功能比如把搜索条件加一个“按药材名称查方剂”或者给统计图表加一个类型切换这种改动虽然工作量不大但能体现你对代码的控制力比背概念有用得多。改动的时候注意不要破坏原有功能改完提交一份备份答辩时展示这个改动的效果就很有说服力。最后再分享一个个人习惯每拿到一个SSM项目我都会先画一遍数据表关系再写一遍查询日志。这个习惯让我在排错和处理别人求助时快很多。你不用画得多规范用笔记软件把表名和连接关系列一列就行分析问题时对照关系图看整个系统的脉络瞬间就清晰了。