ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SSM框架实战:中医养生系统从设计到部署全解析

SSM框架实战:中医养生系统从设计到部署全解析 简介面向毕业设计及SSM框架初学者这份资源是一套基于SpringSpringMVCMyBatis与JSP的中医养生系统完整项目涵盖用户登录管理、养生信息展示等核心模块可作课程设计或毕设参考。压缩包共41.98MB、1509个文件以java源码、jsp页面、css/js前端资源、sql数据库脚本为主另含论文文档、图片及图标字体等资源便于按目录梳理项目结构。目前已有142人学习下载。内容包含前后端完整实现与MySQL数据库设计使用IDEA或Eclipse配合Maven即可导入运行有助于理解SSM框架整合流程、MyBatis数据操作及JSP视图层交互适合需要完整项目案例进行二次开发或学习提升的读者。1. 从一家养生馆的预约台账说起三年前我接手过一个社区中医养生馆的信息化需求推拿师排班靠纸质本、体质档案散落在word里、养生方案全靠口头交代最要命的是周末预约高峰前台既要接电话又要翻台账一小时能错三单。当时客户预算不高、团队就两个人、服务器是一台2核4G的云主机选型只有一条路不引入重型中间件用最传统但最稳的SSMSpringSpringMVCMyBatis框架把系统立起来。这篇博客就顺着这个标题讲讲一个基于SSM的中医养生系统从设计到落地的完整路径——三层架构怎么切、JSP页面怎么做数据渲染、养生业务表怎么设计、最后怎么部署到Tomcat不踩坑。内容面向做Java Web的开发者无论是你正在做SSM课程设计、毕业设计还是接小项目需要一套可复用的骨架这篇都能给你直接能抄的代码和参数。2. SSM框架整合与三层架构边界划分2.1 为什么中医养生系统仍值得选SSM而不是Spring Boot接中医养生这类业务系统时经常有人问2024年了为什么不直接Spring Boot我的回答是如果项目要求打包成war部署到老版本Tomcat、客户现场可能没有外网依赖拉取、或者交付物需要附带完整工程源码供二次开发SSM依然有它的生态惯性。更重要的是SSM把配置暴露在外正好适合用来理解Spring容器、SpringMVC请求链路和MyBatis会话管理这三条主线——对后续维护和面试都有价值。一个典型的SSM工程目录结构是这样的ssm-health/ ├── pom.xml ├── src/main/java │ ├── com/health/controller │ ├── com/health/service │ ├── com/health/mapper │ ├── com/health/entity │ └── com/health/config ├── src/main/resources │ ├── spring/spring-context.xml │ ├── spring/spring-mvc.xml │ └── mybatis/mybatis-config.xml └── src/main/webapp ├── WEB-INF/jsp └── static分层原则是严格的单向依赖controller依赖service接口service依赖mapper接口mapper只对MyBatis负责。严禁在controller里直接注入SqlSessionTemplate——一旦绕过service事务边界声明式事务就会失效。2.2 核心配置文件的必调参数2.2.1 Spring容器配置spring-context.xmlcontext:component-scan base-packagecom.health context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit destroy-methodclose property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/health_db?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword valueroot/ property nameinitialSize value5/ property nameminIdle value5/ property namemaxActive value50/ property namemaxWait value60000/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mybatis/mapper/*.xml/ property nametypeAliasesPackage valuecom.health.entity/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.health.mapper/ /bean注意context:component-scan里的 exclude-filter 是剔除Controller——否则SpringMVC的子容器和根容器会重复扫描导致AOP事务代理失效。maxActive我习惯设为50对单机Tomcat足够maxWait60000毫秒避免高并发下连接池打满后线程无限阻塞。serverTimezoneAsia/Shanghai是针对MySQL 8.x必须加的否则日期字段会报空指针或8小时偏移。2.2.2 SpringMVC请求链路配置spring-mvc.xmlmvc:annotation-driven/ mvc:resources mapping/static/** location/static// bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean bean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver property namemaxUploadSize value10485760/ property namedefaultEncoding valueUTF-8/ /bean养生系统里用户上传体质测评报告、养生食谱图片很常见MultipartResolver的maxUploadSize建议设成10MB太小用户传不了舌诊照片太大会拖垮Tomcat的默认请求线程。完整的web.xml里除了ContextLoaderListener和DispatcherServlet还可以顺手改一下session-timeout到60分钟——养生系统的用户往往是中老年人浏览速度慢30分钟默认超时会被频繁踢下线。3. JSP展示层设计与养生业务的前后端交互3.1 从JSP脚本片段到JSTLEL表达式很多SSM项目里的JSP还停留在% for(int i0;ilist.size();i){ %这种Scriptlet时代。但碰到中医养生系统这种页面里既有体质分型列表、又有养生方案卡片的场景Scriptlet写出来的页面基本没法维护。我一般会强制自己用JSTLEL。先看一个养生方案列表页的核心片段% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % div classhealth-card-list c:forEach items${pageInfo.list} varprogram div classcard h3${program.title}/h3 p适用体质${program.constitutionName}/p p 最近更新 fmt:formatDate value${program.updateTime} patternyyyy-MM-dd HH:mm/ /p a href${pageContext.request.contextPath}/program/detail/${program.id}查看方案/a /div /c:forEach /div${pageInfo.list}是PageHelper分页插件返回的PageInfo对象属性直接解析User实体里的字段。不用在JSP里写任何Java代码EL表达式按属性名自动匹配getter。注意pageContext.request.contextPath这个EL内置对象写死在href里的/ssm-health/一旦上下文路径改了所有链接都会404。3.2 养生测评页面的Ajax实时校验体质测评是中医养生系统的核心交互——用户填一套问卷系统实时计算体质分型。如果每次点下一步都整页刷新体验很差。常见做法是jQuery发Ajax到SpringMVC的ResponseBody接口。前端那边应该有个问卷步骤条每完成一题就自动请求一次后端校验。后端Controller对应的代码是这样的Controller RequestMapping(/assessment) public class AssessmentController { Resource private AssessmentService assessmentService; ResponseBody RequestMapping(value /validate, method RequestMethod.POST) public MapString, Object validateStep(RequestBody AssessmentItem item) { MapString, Object result new HashMap(); boolean valid assessmentService.validateAnswer(item); result.put(valid, valid); if (!valid) { result.put(message, 题目 item.getQuestionId() 的选项需从1-5分制中选择); } return result; } }注意RequestBody POST前端jQuery里要对应设置$.ajax({ url: ctxPath /assessment/validate, type: POST, contentType: application/json, data: JSON.stringify({questionId: 3, answerScore: 2}), dataType: json, success: function(resp) { if (!resp.valid) { $(#q- 3).addClass(has-error); } else { $(#q- 3).removeClass(has-error); } } });这里最常踩的坑是两个一是忘记设置contentType: application/json结果Spring一直解析不了参数二是把RequestBody和RequestParam混用同一个方法里同时出现会让Spring不知道从哪个位置取值。做Ajax校验时整个方法体只处理JSON体需要额外参数就从URL的query string里拿。3.3 JSP分页组件与PageInfo的联动列表页分页是JSP电商项目里的高频需求。SSM里用PageHelper分页只需要在查询前一行写PageHelper.startPage(pageNum, pageSize); ListProgram list programMapper.selectAll(); PageInfoProgram pageInfo new PageInfo(list);这里有个必须注意的细节PageHelper.startPage() 只作用于紧随其后的第一条查询语句。如果你在startPage和selectAll之间多写了一条无关的查询分页就加到了错误的SQL上。还有pageSize必须做上限校验不然用户手动传?pageSize99999就能一次拉全库数据。我一般会在service层直接截断pageSize Math.min(pageSize, 50)。JSP底部分页栏保留一个简单的页码链接生成逻辑用c:forEach遍历pageInfo.getNavigatepageNums()。记得在前端同时展示当前 ${pageInfo.pageNum}/${pageInfo.pages} 页共 ${pageInfo.total} 条记录这样用户对数据量有清晰认知。4. 中医养生核心业务模块落地与MyBatis持久化4.1 业务模块边界划分一个标准的中医养生系统我拆成五个独立服务模块模块核心实体关键操作用户管理User注册、登录、角色鉴权体质测评Assessment, Question问卷提交、体质分型计算养生方案Program, ProgramItem方案推荐、收藏、收藏列表预约管理Appointment创建预约、排班校验、取消健康档案HealthRecord历次测评记录、趋势查询模块划分的核心原则是「高内聚、低耦合」——预约模块的Service可以调用用户模块的Mapper但反过来不允许。一旦出现跨模块循环依赖Spring容器启动时就会报BeanCurrentlyInCreationException。4.2 MyBatis动态SQL处理复杂养生方案查询养生方案推荐这个场景查询条件非常不固定用户选了「平和质」、希望调理方向是「失眠」、预算在500以内、最好有食疗方案。四个条件每个都可能为空写静态SQL就得拼四个if——MyBatis的动态SQL就是为这个场景准备的。select idsearchPrograms resultTypecom.health.entity.Program SELECT id, title, constitution_id, direction, budget, content FROM health_program where if testconstitutionId ! null AND constitution_id #{constitutionId} /if if testdirection ! null and direction ! AND direction LIKE CONCAT(%, #{direction}, %) /if if testmaxBudget ! null AND budget lt; #{maxBudget} /if if testwithFoodTherapy ! null and withFoodTherapy AND content LIKE %食疗% /if /where ORDER BY choose when testsortBy hotview_count DESC/when when testsortBy newcreate_time DESC/when otherwiseid ASC/otherwise /choose /select单看XML重点要理解三个标签的分工where会自动去掉第一个AND前缀这样不需要写11这种凑数条件if的test表达式里字符串判空写! 而不是.length() 0前者可读性更好choose用于多分支互斥判断比连续if更语义化。lt;里的小于号必须转义否则XML解析直接报错。4.3 Service层事务控制与操作日志养生方案的浏览量自增、收藏数累加、以及用户测评记录写入这三个操作必须在一个事务里完成——不然用户测评成功但方案推荐记录丢了下次登录就只剩个测评结果没有推荐。事务控制直接上Spring声明式注解Service public class ProgramServiceImpl implements ProgramService { Resource private ProgramMapper programMapper; Resource private HealthRecordMapper healthRecordMapper; Override Transactional(rollbackFor Exception.class) public void generateRecommendation(Long userId, String constitutionType) { ListProgram programs programMapper.selectByConstitution(constitutionType); HealthRecord record new HealthRecord(); record.setUserId(userId); record.setConstitutionType(constitutionType); record.setRecommendationCount(programs.size()); healthRecordMapper.insert(record); programMapper.incrementViewCount(programs.stream().map(Program::getId).toList()); } }Transactional(rollbackFor Exception.class)是压箱底的配置Spring默认只对RuntimeException回滚对受检异常不回滚。这里手动指定所有Exception都回滚避免Service内部的受检异常导致数据不一致。事务只配在Service层方法上Controller调Service、Service调Mapper之间的事务传播默认的REQUIRED够用。5. 数据库表结构设计、MySQL参数调优与慢查询排查5.1 核心表DDL与设计思路养生系统的核心表我一般建四张用户表、测评问卷表、测评记录表、养生方案表。习惯用的是InnoDB引擎、utf8mb4字符集——utf8mb4不是可选项是必选项中医养生内容里大量涉及生僻字和特殊符号比如某些药材名的Unicode编码utf8存不下。CREATE TABLE health_program ( id int NOT NULL AUTO_INCREMENT COMMENT 主键, title varchar(128) NOT NULL COMMENT 方案标题, constitution_id int NOT NULL COMMENT 适用体质ID, direction varchar(64) DEFAULT NULL COMMENT 调理方向失眠/调理肠胃/祛湿, budget decimal(10,2) DEFAULT NULL COMMENT 预估预算, content text NOT NULL COMMENT 方案详细内容, view_count int DEFAULT 0 COMMENT 浏览量, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_constitution_direction (constitution_id, direction) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT养生方案表;CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP这行不需要代码维护更新时间MyBatis的insert和update都少一列。联合索引idx_constitution_direction对应第4节那个动态SQL的前两个查询条件注意动态SQL里的LIKE %...%是没法走索引的所以方向字段最好做成枚举值存字符串查询时用等值匹配而不是模糊匹配。5.2 MySQL数据库连接与连接池参数在spring-context.xml里已经配过数据源但这里有几个参数要单独拎出来说。DruidDataSource的testWhileIdle默认是true意味着连接空闲60秒后会做一次校验。如果数据库的wait_timeout设置小于连接池的minEvictableIdleTimeMillis空闲连接会被MySQL服务端杀掉客户端首次请求时报Communications link failure。我一般把minEvictableIdleTimeMillis设置成50000比wait_timeout短一截让连接在MySQL回收之前自己先退役。如果换到MySQL 8.0连接驱动类从com.mysql.jdbc.Driver换成com.mysql.cj.jdbc.Driver这就是标题里数据库热词连带的内容。同时URL参数里的useSSLfalse建议显式加避免8.0默认开启SSL握手带来的20ms以上延迟——内网部署不需要SSL。5.3 慢查询排查方法SSM项目后端的慢查询最经典的症状是首页养生方案列表打开要3秒以上数据库CPU占用率飙到80%。排查步骤如下-- 查看MySQL慢查询日志是否开启 SHOW VARIABLES LIKE slow_query_log; SHOW VARIABLES LIKE long_query_time; -- 开启慢查询日志临时生效 SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 1; -- 查看最近慢SQL SELECT query_time, rows_examined, sql_text, current_user FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;拿到慢SQL后第一件事不是加索引而是用EXPLAIN看执行计划EXPLAIN SELECT id, title FROM health_program WHERE constitution_id 1 AND direction 失眠 ORDER BY create_time DESC\G如果type列是ALL说明是全表扫描如果rows远大于真实行数说明索引选择不对。ORDER BY create_time和WHERE里的constitution_id不在同一个索引上MySQL可能选错索引这时要把排序字段也放进联合索引里KEY idx_cons_dir_time (constitution_id, direction, create_time)让排序直接走索引.6. 用Tomcat部署war包前后的环境校验与验证清单6.1 部署前必查的三个文件找到Tomcat的conf/context.xml确认Context里没有设置antiResourceLockingtrue。这个参数在Windows系统下会锁定WEB-INF/lib下的jar文件第二次重新部署war时杀不掉旧的类加载器最终报java.lang.OutOfMemoryError: PermGen space。conf/server.xml里Host的appBase和项目打包的war文件名要一致。比如war包叫health.war访问路径就是/health/。如果你改了上下文路径JSP里所有写死/ssm-health/的链接全得改。这就是第3节代码里用pageContext.request.contextPath动态拼接的原因——部署时永远不用改一行路径。最后是JDK版本。SSM项目一般用JDK8编译如果你的服务器装了JDK11或者更高版本要注意Tomcat 8.5以下版本跑不了JDK9以上的编译产物报UnsupportedClassVersionError。先执行java -version确认版本再用./startup.sh tail -f logs/catalina.out看启动日志确认无Exception。6.2 接口压测与数据库状态验证项目部署完我不急着点页面先看接口响应时间和数据库连接数# 压测登录接口 ab -n 200 -c 20 http://localhost:8080/health/user/login # 验证连接池回收状态 mysql -uroot -p -e SHOW STATUS LIKE Threads_connected;Threads_connected数值稳定在连接池的minIdle到maxActive之间说明连接池工作正常。如果ab压测200个请求后连接数一直卡在200不释放得回看DruidmaxActive是不是设太大TimedWait连接泄漏了。通常压测后Threads_connected应回落并稳定在个位数。# 查看Tomcat的JVM内存占用 jstat -gcutil $(pgrep java) 1000jstat -gcutil输出中的FGC如果持续增长且Old区涨到85%以上就得调CATALINA_OPTS-Xms512m -Xmx1024m。养生系统这种业务并发量不大1024M的上限完全够用堆设太大反而让GC停顿变长。6.3 一个免费好用的小技巧压测后顺手看线程快照诊断系统卡顿的另一个有效技巧是抓线程快照。压测结束后用jstack $(pgrep java) /tmp/thread_dump.txt然后grephttp-nio-8080-exec-开头的线程状态。大量线程卡在WAITING (on object monitor)是Druid连接池打满卡在RUNNABLE且堆栈落在socketRead是用户请求慢——两者处理方向完全相反一个是加大maxActive一个是优化SQL。有了线程快照和慢查询日志对应看排查效率翻倍。本文还有配套的精品资源点击获取
返回列表