SSM框架实战:牙齿护理系统从数据库到部署全解析

发布时间:2026/9/17 15:54:07
SSM框架实战:牙齿护理系统从数据库到部署全解析 简介这份资源是一份基于SSM框架的牙齿护理系统设计与研究文档面向Java开发学习者、毕业设计选题学生以及医疗信息化方向的研究者解决口腔护理线上预约与系统设计参考的问题。文档系统阐述了SSMSpringSpringMVCMyBatis、前后端分离模式、Vue-admin前端框架、Nginx代理及MyBatis数据库操作等核心知识点并给出普通用户、医生、管理员、超级管理员四种角色的功能设计思路还涵盖可行性分析、需求分析和国内外研究现状可作为课程设计或毕业设计文档模板。压缩包仅含1个docx文件体积约1.3MB内容精炼集中便于快速阅读和参考。目前已有121人学习下载适合需要了解牙齿护理系统架构、SSM整合方案或撰写相关设计文档的读者。1. 牙齿护理系统为什么还在用 SSM以及这套方案解决什么问题很多口腔诊所的前台还在用 Excel 排班复诊提醒靠护士下班前翻纸质病历打电话。做过这类管理系统的人会有一个反直觉的体会SSM 不是最新技术却是能把需求最快落地的组合。Spring 管对象和事务Spring MVC 管请求路由MyBatis 管 SQL 与 Java 对象的转换三个框架各管一段出了问题能顺着调用链直接定位。下面按数据库设计、后端接口、前端联调、部署测试这条线把牙齿护理系统的患者档案、预约排班、护理计划与复诊提醒完整拆开讲给正在做课程设计或者准备接手类似 SSM 项目的读者一条能直接照着走的路。2. SSM 在牙齿护理系统里的分工与核心配置SSM 能在这类管理系统里站稳不是因为某个框架有多强而是三个框架的边界恰好贴合业务系统的分层习惯。Spring 的 IoC 容器负责把 Service、Mapper、事务管理器这些对象串起来Spring MVC 聚焦 HTTP 层把 URL、参数、JSON 和 JSP 视图之间的转换处理掉MyBatis 把 SQL 写在 XML 里比 Hibernate 全自动映射更容易控制分页和多表 join。牙齿护理系统恰恰是典型的多表业务患者档案关联预约记录预约记录关联诊疗方案诊疗方案又延伸出护理提醒这种场景用 MyBatis 手写 SQL 反而直观。有人会问为什么不做 Spring Boot。如果课程设计要求拆解 SSM 配置过程Spring Boot 的自动配置反而把核心内容藏起来了。答辩时如果能说清 web.xml、applicationContext.xml、spring-mvc.xml 的加载顺序和数据源装配方式效果比单纯说“用了 Spring Boot”好不少。2.1 工程目录与依赖坐标搭这套系统时一般会按 controller / service / mapper / entity 四层分包配置文件放在 resources 下JSP 放进 webapp/WEB-INF/views。目录结构本身没有标准答案但要把 web.xml、两份 spring 配置和 mybatis 配置分开否则后面改数据源或加拦截器时会互相干扰。src/main/java ├── com.clinic.config ├── com.clinic.controller ├── com.clinic.service │ └── impl ├── com.clinic.mapper ├── com.clinic.entity └── com.clinic.common src/main/resources ├── spring │ ├── applicationContext.xml │ └── spring-mvc.xml ├── mybatis │ ├── mybatis-config.xml │ └── mapper ├── jdbc.properties └── sql/init.sql webapp/WEB-INF ├── web.xml └── viewspom.xml 里最核心的依赖是 spring-webmvc、mybatis、mybatis-spring 和数据库驱动。Spring 版本 5.3.x、mybatis-spring 2.0.x 是一组常用搭配JDK 8 环境下用这套能减少兼容问题。dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version5.3.20/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.8/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.29/version /dependencymybatis-spring 的版本要和 Spring 大版本匹配Spring 5 不要配 mybatis-spring 1.x。mysql 驱动如果是 8.x连接 URL 里要带 serverTimezoneAsia/Shanghai否则日期查询会差 8 小时预约时间显示错位。2.2 web.xml 和两份 Spring 配置各管什么web.xml 只做两件事加载根容器和注册前端控制器。根容器先于 DispatcherServlet 初始化数据源、事务、Service 和 Mapper 都会被 Spring 管理起来DispatcherServlet 加载的 spring-mvc.xml 只管 Web 层这样 Service 层可以独立于 Web 层测试。context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mappingload-on-startup 设成 1表示 Tomcat 启动时就初始化 DispatcherServlet而不是等第一个请求进来才初始化。url-pattern 配成 / 会拦截所有请求所以 spring-mvc.xml 里必须放行 /static/** 下的 CSS、JS 和图片。spring-mvc.xml 开启注解驱动、扫描 controller、配置视图解析器mvc:annotation-driven / context:component-scan base-packagecom.clinic.controller / mvc:resources mapping/static/** location/static/ / bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views/ / property namesuffix value.jsp / /beanmvc:annotation-driven 负责注册 JSON 转换器、参数解析器和类型转换器。InternalResourceViewResolver 的前缀后缀拼起来就是 JSP 真实路径比如 Controller 返回 appointment/list会找 /WEB-INF/views/appointment/list.jsp。JSP 放在 WEB-INF 下可以防止浏览器直接访问必须经过 Controller 转发。2.3 MyBatis 与 Spring 整合的两个关键点在 applicationContext.xml 里配置 SqlSessionFactoryBeandataSource 指向 Druid 数据源mapperLocations 指向 mapper XML 目录typeAliasesPackage 让 XML 里可以直接写 resultTypePatient不用写全限定类名。bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nametypeAliasesPackage valuecom.clinic.entity/ property namemapperLocations valueclasspath:mybatis/mapper/*.xml/ property nameconfiguration bean classorg.apache.ibatis.session.Configuration property namemapUnderscoreToCamelCase valuetrue/ /bean /property /beanmapUnderscoreToCamelCase 打开后数据库字段 real_name 会映射到实体属性 realName省掉大量手写 resultMap。牙齿护理系统的预约和治疗记录表下划线字段很多这项配置能显著减少映射代码。第二点是配置 MapperScannerConfigurer扫描 mapper 接口。接口名、方法名和 XML 的 namespace、id 对应后Spring 会生成代理实现Service 里可以直接 Autowired。bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.clinic.mapper/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /beanMyBatis 的 SqlSession 生命周期由 Spring 管理不需要手动关闭。数据源参数建议放在 jdbc.properties 里Druid 的 initialSize 设 5、maxActive 设 20对毕业设计规模已经足够。3. 牙齿护理系统的表设计与 MyBatis 映射数据库设计是这个系统不被扣分的重点。先看表关系sys_user 存登录账号patient 存患者信息doctor 存医生信息和擅长方向appointment 存预约记录treatment_record 存每次看诊的治疗内容、医嘱和费用nursing_plan 存护理方案与下次复诊日期提醒任务每天查这张表。3.1 六张核心业务表表名核心字段说明sys_userid, username, password, role系统登录账号patientid, real_name, phone, allergy_history, id_card患者基本档案doctorid, real_name, dept, specialty, status医生信息appointmentid, patient_id, doctor_id, appt_start, appt_end, status预约记录treatment_recordid, appointment_id, diagnosis, treatment, fee, next_visit_date诊疗与医嘱nursing_planid, patient_id, doctor_id, plan_name, next_remind_time, status护理与复诊提醒设计时要把 status 字段当成一等公民。预约状态用 0 待就诊、1 已完成、2 已取消、3 爽约护理计划状态用 0 未开始、1 执行中、2 已完成。实体类不要用 String 直接存状态用 Integer 加常量类或枚举后面做统计时不容易写错。牙科系统比较特殊的一点是过敏史和既往病史。这两个字段要单独放在 patient 表不要塞进治疗记录的 description 里。后续做治疗风险提示时一条 SQL 就能把高风险患者捞出来按过敏原和患者姓名筛人比全文搜索高效得多。提示费用字段用 decimal(10,2)不要用 float否则金额会出现精度尾差打印结算单时容易被患者质疑。3.2 患者详情的一对多 resultMap患者详情页需要同时展示预约记录和治疗历史这是一个典型的一对多查询。先查患者再循环查预约会产生 N1 问题数据量小的时候不明显页面一多就慢。用 resultMap 的 collection 可以在一条 SQL 里完成聚合。mapper namespacecom.clinic.mapper.PatientMapper resultMap idpatientDetailMap typePatient id propertyid columnid/ result propertyrealName columnreal_name/ result propertyphone columnphone/ collection propertyappointments ofTypeAppointment id propertyid columnappt_id/ result propertyapptStart columnappt_start/ result propertystatus columnappt_status/ /collection /resultMap select idselectPatientDetail resultMappatientDetailMap SELECT p.id, p.real_name, p.phone, a.id AS appt_id, a.appt_start, a.status AS appt_status FROM patient p LEFT JOIN appointment a ON a.patient_id p.id WHERE p.id #{id} /select /mapperresultMap 里 Patient 主键用 id 属性collection 用 ofType 声明集合元素类型。SQL 使用 LEFT JOIN如果同一患者有两条预约记录MyBatis 会根据 resultMap 的主键 id 判断是否已经创建过 Patient 对象相同则只追加 appointment 到集合中不会生成重复患者对象。collection 子项的 id 列一定要起别名比如 appt_id避免和 patient.id 冲突后前端拿到错误 ID。如果页面只展示最近三次预约可以在 SQL 里先用子查询取 top 3再 join 患者表避免一次性加载全部历史记录后续出现慢查询时优先检查这里。3.3 动态 SQL 筛选预约记录预约列表页通常有三个筛选条件医生、日期范围、状态。写三个不同 SQL 很笨MyBatis 的 where if 可以动态拼接一个方法就够。select idselectAppointmentList resultTypeAppointment SELECT a.*, p.real_name AS patientName, d.real_name AS doctorName FROM appointment a LEFT JOIN patient p ON a.patient_id p.id LEFT JOIN doctor d ON a.doctor_id d.id where if testdoctorId ! null AND a.doctor_id #{doctorId} /if if teststartTime ! null and startTime ! AND a.appt_start gt; #{startTime} /if if teststatus ! null AND a.status #{status} /if /where ORDER BY a.appt_start DESC LIMIT #{offset}, #{limit} /selectwhere 标签会自动处理第一个条件前面的 AND这是 MyBatis 里最容易被忽略的细节。status 等于 0 时if 判断status ! null是成立的所以“待就诊”状态筛选不会丢数据。日期字符串比较时MySQL 会自动把字符串转成时间类型但前后端格式要统一成 yyyy-MM-dd HH:mm:ss否则会出现边界差一分钟的 bug。这里用 LIMIT 手写分页简单项目足够。如果条件继续变多可以引入 PageHelper但要检查它生成的 count 语句在慢查询日志里是否异常它的 count 会去掉 order by但不会自动去掉多余的 left join。4. 预约接口与护理计划的事务实现预约业务是牙齿护理系统里最容易出并发问题的地方。很多同学会把可约时段先查出来展示给前端用户考虑几分钟后提交后台却没有占用该时段结果两个人同时预约了同一个医生的同一个时间段。4.1 预约接口设计与参数绑定接口按 JSON 风格设计预约创建用 POST /api/appointment/create取消预约用 POST 加参数。不要用 GET 做写操作GET 会被浏览器缓存和预加载重复提交后患无穷。RestController RequestMapping(/api/appointment) public class AppointmentController { Autowired private AppointmentService appointmentService; PostMapping(/create) public ResultVoid create(RequestBody Valid AppointmentCreateDTO dto) { appointmentService.createAppointment(dto); return Result.success(); } PostMapping(/cancel) public ResultVoid cancel(RequestParam Integer appointmentId, RequestParam String cancelReason) { appointmentService.cancelAppointment(appointmentId, cancelReason); return Result.success(); } }RestController 是 Controller 和 ResponseBody 的组合返回值会通过 Jackson 序列化成 JSON。DTO 里用 NotBlank、NotNull 做基础校验实体类不要直接作为 Controller 入参因为前端传来的字段里可能带 role、status 等不该由用户提交的字段。日期字段建议加JsonFormat(pattern yyyy-MM-dd HH:mm, timezone GMT8)否则 Jackson 默认序列化成时间戳前端input typedatetime-local拿到数字会直接解析失败。4.2 Service 层事务边界与防重复预约预约的检查与插入必须在一个事务里。只做查询再插入不做数据库约束兜底并发测试一压就会露馅。Transactional(rollbackFor Exception.class) public void createAppointment(AppointmentCreateDTO dto) { Patient patient patientMapper.selectById(dto.getPatientId()); if (patient null) { throw new BusinessException(患者档案不存在); } int count appointmentMapper.countByDoctorAndTime( dto.getDoctorId(), dto.getApptStart(), dto.getApptEnd(), AppointmentStatus.WAITING); if (count 0) { throw new BusinessException(该时段已被预约); } Appointment appointment new Appointment(); BeanUtils.copyProperties(dto, appointment); appointment.setStatus(AppointmentStatus.WAITING); appointmentMapper.insert(appointment); }Transactional 默认只回滚 RuntimeExceptionrollbackFor 设成 Exception.class 后BusinessException 这类业务异常也会触发事务回滚。count 加 insert 在并发下仍有间隙所以还要在 appointment 表上建唯一约束用 doctor_id、appt_start 和 status 的组合作为唯一键第二个请求插入时抛出 DuplicateKeyException事务跟着回滚前端会收到“该时段已被预约”的提示。BusinessException 放在 common 包统一维护错误码。不要把 Exception 的 message 直接返回给前端否则 SQL 报错信息会暴露表结构和连接串。4.3 参数校验放在 Controller 还是 ServiceValid 只适合做字段级校验比如电话必填、时间格式。像“该时段已约满”“患者正在治疗中”这类跨表校验必须放 Service 层因为 Controller 拿不到完整上下文。取消预约时也建议做状态机控制只在 WAITING 状态允许取消已完成或已取消状态不允许二次操作。把状态流转收拢到一个方法里前端和 Controller 不直接 setStatus后续加需求时只需要改 Service 内部。5. 前端页面与系统联调把预约信息送到后端前端不打算用 Vue 的话JSP 加 Bootstrap 是 SSM 最省事的组合。公共头部和导航栏用 include 抽出来页面只保留自己的内容块。预约页面里患者和医生信息在用户登录后由后端放进 ModelAndView前端下拉框直接渲染。EL 表达式在 Servlet 3.0 之后默认开启用 JSTL 时注意状态码比较要写${appointment.status 0}Integer 会自动拆箱。5.1 Ajax 提交预约表单传统 form 提交会刷新页面错误提示不友好。用 Ajax 提交可以保持页面不刷新后端返回 Result 对象带 code 和 message前端统一弹提示。$(#appointmentForm).on(submit, function (e) { e.preventDefault(); $.ajax({ url: ctxPath /api/appointment/create, type: POST, contentType: application/json;charsetUTF-8, data: JSON.stringify({ patientId: $(#patientId).val(), doctorId: $(#doctorId).val(), apptStart: $(#apptStart).val(), apptEnd: $(#apptEnd).val(), description: $(#description).val() }), success: function (resp) { if (resp.code 0) { location.reload(); } else { alert(resp.message); } }, error: function (xhr) { console.error(xhr.responseText); } }); });ctxPath 通过 JSP 里的var ctxPath %request.getContextPath()%;获取避免请求路径少了项目名而 404。contentType 必须是 application/json;charsetUTF-8否则后端 RequestBody 接不到数据。success 回调里先判断业务 code 再决定刷新或提示HTTP 200 只能说明请求到了后端业务失败也可能返回 200只是 code 非 0。5.2 联调时的三个常见问题第一个是 404 加 500。路径对但 404 时检查 DispatcherServlet 的 url-pattern 是否覆盖以及 Controller 是否漏掉 ResponseBody500 时把xhr.responseText打印到浏览器控制台多半是 Jackson 序列化失败。第二个是中文乱码。Tomcat 8 以上对 POST 的 UTF-8 处理需要在 web.xml 配 CharacterEncodingFilter并且把 forceEncoding 设为 true否则 JSON 里的中文会变成问号。第三个是日期格式。前端datetime-local提交的值是2025-03-20T10:30后端 DTO 用JsonFormat后直接转 LocalDateTime 会报错建议在 DTO 里用 String 接再由 Service 层统一转成 Date 或 LocalDateTime这样前端格式错误时能给出明确提示。6. 部署 Tomcat 与三个容易被忽视的加分细节6.1 Maven 打包与部署项目确认可运行后在项目根目录执行mvn clean package -DskipTests命令会跳过单元测试并打包 war。war 文件名由 pom 中 finalName 决定。把它复制到 Tomcat 的 webapps 目录启动后访问 http://localhost:8080/项目名/。如果部署后静态资源 404先确认 war 解压目录下 webapp/static 是否存在IntelliJ IDEA 内置 Tomcat 和外部 Tomcat 的解压路径有差别建议用外部 Tomcat 验证一次。6.2 数据库与日志配置建表语句放到 resources/sql/init.sql在项目文档里写明执行顺序。连接参数放在 jdbc.propertiesDruid 的 initialSize 设 5maxActive 设 20system 规模完全够用。日志用 Slf4j 加 LogbackMyBatis 的日志级别调成 DEBUG 后可以看到预编译 SQL 和被替换进去的参数值这个技巧在排查预约时间边界问题时非常有用。6.3 三个加分细节第一个是密码加密。不要用 MD5 明文存储用 BCrypt 加盐更稳妥。Spring Security 里的 BCryptPasswordEncoder 可以单独拿出来用不引入完整安全框架。第二个是分页查询。手写 LIMIT 可行但 PageHelper 的PageHelper.startPage(pageNum, pageSize)一行就能完成物理分页注意它只对其后第一条 SQL 生效中间不要插入其他查询。第三个是复诊提醒。在配置里开启 task 注解驱动Service 中加Scheduled(cron 0 0 8 * * ?)每天 8 点扫护理计划表把 next_remind_time 小于当天日期的记录生成提醒消息。定时任务要注意幂等推荐在 nursing_plan 表加 remind_status 字段任务扫描时带上remind_status 0条件处理完立刻更新为 1这样即使任务重复执行或者多实例部署也不会产生重复的复诊提醒。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询