公司动态

SSM框架实战:从零构建客户管理系统,掌握Java Web开发核心

📅 2026/9/2 6:45:07
SSM框架实战:从零构建客户管理系统,掌握Java Web开发核心
简介本资源是一套面向计算机专业本科生毕业设计及Java初学者实战训练的客户关系管理CRM系统基于SSMSpringSpringMVCMyBatis框架开发覆盖客户资料管理、服务跟踪、交易记录、客户等级与开发进度等核心业务场景。压缩包共3个文件1个主程序ZIP、1个SQL数据库脚本、1个项目说明TXT总大小25.18MB其中SQL脚本可一键初始化MySQL数据结构TXT文档详述环境配置与运行步骤主ZIP含完整Eclipse工程源码支持JDKTomcat快速部署。已有1111人学习下载系统采用分层架构与角色权限控制内置管理员、客户经理、营销主管三类用户功能模块清晰、界面简洁、调试完备可直接用于毕设答辩或Java Web开发能力强化训练。1. 项目概述一个典型的SSM毕设项目能教会你什么又到了一年一度的毕业季对于计算机相关专业的同学来说毕业设计是绕不过去的一道坎。很多同学一听到“毕设”两个字就头疼感觉无从下手特别是那些平时实践机会不多的同学。如果你正在为“基于SSM的客户资料管理系统”这类题目发愁或者想找一个结构清晰、能跑通、还能学到东西的项目来练手那这篇文章就是为你准备的。这个项目说白了就是一个用Java技术栈实现的、用来管理客户信息的Web系统。它麻雀虽小五脏俱全涵盖了企业级Java开发中最经典、最实用的技术组合SSM框架Spring Spring MVC MyBatis。为什么说它适合毕设和初学者因为它需求明确增删改查客户信息技术栈成熟稳定网上资料丰富而且从数据库设计到前端展示完整地走一遍流程能让你对Web开发有一个系统性的认识。你做完它不仅能交出一份像样的毕业设计更重要的是你能真正理解一个Web应用是如何从零到一搭建起来的这对你后续找工作、做项目有莫大的帮助。2. 项目整体设计与技术选型思路2.1 为什么是SSM框架在开始动手之前我们先聊聊为什么选择SSM作为技术栈而不是更新的Spring Boot。这对于理解项目架构至关重要。SSM是Spring、Spring MVC和MyBatis三个框架的集成。Spring Boot的出现确实简化了配置但对于学习而言SSM能让你更清晰地看到各个组件的职责和它们是如何协作的。Spring Boot帮你把轮子装好了而SSM需要你自己动手组装轮子这个过程能加深你对框架原理的理解。Spring它是整个项目的“大管家”负责管理所有对象Bean的生命周期和依赖关系。比如我们的Service层需要调用Dao层这个“调用关系”就是由Spring通过依赖注入DI来完成的而不是我们在代码里new出来的。这样做的好处是解耦方便测试和维护。Spring MVC它负责处理Web请求。当用户在浏览器点击一个按钮发起一个“添加客户”的请求时这个请求会先到达Spring MVC的前端控制器DispatcherServlet然后由它找到对应的控制器Controller方法来处理。Controller处理完业务逻辑通常调用Service最后返回一个视图比如JSP页面或者数据JSON给浏览器。它是连接用户操作和后端逻辑的桥梁。MyBatis它是一个优秀的持久层框架负责和数据库打交道。我们写SQL语句MyBatis帮我们执行并把数据库返回的结果集自动映射成我们定义好的Java对象。相比于Hibernate的全自动ORMMyBatis半自动的方式让我们对SQL有完全的控制力这对于性能优化和复杂查询来说非常有利也更符合很多国内开发者的习惯。注意对于毕设项目使用SSM而非Spring Boot在答辩时可能更能体现你对基础框架的理解。你可以清晰地阐述web.xml、Spring配置文件、MyBatis配置文件各自的作用这比直接说“Spring Boot自动配置”更有技术深度。2.2 系统核心功能模块拆解一个客户资料管理系统核心功能无非是对客户信息的“增删改查”CRUD。但一个好的设计会让这些操作更安全、更高效、更易用。我们通常会将系统划分为以下几个层次表现层View用户直接操作的界面。这里我们选择经典的JSP JSTL Bootstrap的组合。JSP用于动态生成页面Bootstrap能让我们的界面快速变得美观且响应式适配电脑和手机。为什么不直接用HTML因为我们需要在页面上展示从数据库查出来的动态数据JSP可以嵌入Java代码片段虽然不推荐大量使用或使用JSTL标签库来循环展示客户列表。控制层Controller接收页面请求调用业务逻辑决定返回哪个页面或数据。这是Spring MVC的舞台。每个功能比如“查询客户列表”、“新增客户”都会对应Controller中的一个方法。业务逻辑层Service处理具体的业务规则。比如在新增客户前需要检查手机号格式是否合法、邮箱是否重复等。这一层是核心业务逻辑所在它调用持久层的方法但封装了更复杂的业务判断。持久层Dao/Mapper直接操作数据库执行SQL语句。这一层接口和SQL映射文件XML由MyBatis管理。实体层Entity/Model就是普通的Java类POJO用来对应数据库中的一张表。例如Customer类里面有id、name、phone、email等属性和数据库t_customer表的字段一一对应。这种分层架构的好处是职责清晰任何一层的变化只要接口不变对其他层的影响就最小。比如今天用MySQL明天想换Oracle你只需要修改MyBatis的SQL映射和数据库连接配置Service层以上的代码几乎不用动。2.3 数据库设计要点数据库设计是项目的基石设计得好后期开发事半功倍。对于客户表我们至少需要以下字段CREATE TABLE t_customer ( id int(11) NOT NULL AUTO_INCREMENT COMMENT 客户ID主键, name varchar(50) NOT NULL COMMENT 客户姓名, gender tinyint(1) DEFAULT NULL COMMENT 性别0-女1-男, phone varchar(20) DEFAULT NULL COMMENT 手机号, email varchar(100) DEFAULT NULL COMMENT 电子邮箱, company varchar(200) DEFAULT NULL COMMENT 公司名称, position varchar(100) DEFAULT NULL COMMENT 职位, address varchar(500) DEFAULT NULL COMMENT 地址, remark text COMMENT 备注, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_phone (phone), -- 手机号唯一索引 KEY idx_name (name) -- 姓名普通索引方便按姓名搜索 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT客户信息表;设计思考id主键自增。这是表的唯一标识。gender用tinyint存储比varchar存储“男”、“女”更节省空间。在代码中用枚举类来映射。phone设为唯一约束因为手机号通常是客户的唯一标识。这里有个坑如果业务允许客户没有手机号唯一约束可能会因为多个NULL值而失效具体看数据库版本需要根据实际情况处理。create_time和update_time这两个时间戳字段强烈建议加上。它们对于数据追溯、审计和分析非常有价值。ON UPDATE CURRENT_TIMESTAMP可以让记录在更新时自动刷新update_time。utf8mb4字符集支持存储Emoji等特殊字符避免出现乱码问题。索引在phone和name上建索引。phone是唯一索引保证唯一性并加速基于手机号的查询。name是普通索引加速按姓名模糊搜索LIKE ‘%张%’。但要注意索引不是越多越好会影响插入和更新速度。3. 开发环境搭建与项目初始化3.1 工具选型与配置工欲善其事必先利其器。一套顺手的开发环境能极大提升效率。JDK选择JDK 8或JDK 11。JDK 8依然是目前企业中使用最广泛的LTS版本兼容性最好。建议从Oracle官网或AdoptOpenJDK下载。IDEIntelliJ IDEA Ultimate学生可以免费申请许可是首选。它对Java和Spring系列框架的支持是最好的智能提示、代码生成、调试功能都非常强大。Eclipse也可以但效率和体验上IDEA更优。项目管理与构建工具Maven。它帮你管理项目依赖Jar包你只需要在pom.xml文件中声明需要什么框架、什么版本Maven会自动从中央仓库下载并解决包之间的依赖关系。再也不用手动下载一堆Jar包了。数据库MySQL 5.7或8.0。安装时记住root密码。同时需要一个图形化的数据库管理工具Navicat或DBeaver免费都可以方便你执行SQL脚本、查看数据。应用服务器本地开发使用Tomcat 8.5或9。IDEA可以很方便地集成Tomcat进行热部署调试。实操心得在IDEA中配置Maven时建议使用自己下载的Maven并修改其conf/settings.xml文件配置阿里云的镜像仓库这样下载依赖包的速度会飞起。同时将本地仓库路径改到非系统盘避免C盘爆满。3.2 创建Maven Web项目与SSM整合这是项目的基础骨架搭建步骤虽多但每一步都有其意义。创建项目在IDEA中选择新建Maven项目选择maven-archetype-webapp原型。这会生成一个标准的Web项目结构。完善目录结构Maven原型生成的结构不完整需要手动补全。这是标准的SSM项目目录src/main/java com.yourcompany.crm.controller // 控制层 com.yourcompany.crm.service // 业务层接口 com.yourcompany.crm.service.impl // 业务层实现 com.yourcompany.crm.dao // 数据层接口MyBatis Mapper接口 com.yourcompany.crm.entity // 实体类 com.yourcompany.crm.config // 配置类可选如果用Java Config src/main/resources /spring // Spring配置文件 applicationContext.xml spring-mvc.xml /mybatis // MyBatis配置文件 mybatis-config.xml /mappers // MyBatis的SQL映射XML文件 CustomerMapper.xml jdbc.properties // 数据库连接属性文件 log4j2.xml // 日志配置文件 src/main/webapp /WEB-INF web.xml // Web部署描述符 /static /css /js /img /WEB-INF/jsp // JSP页面存放目录编写pom.xml这是项目的“食材清单”。你需要声明依赖核心依赖包括Spring Corespring-context,spring-beans,spring-coreSpring MVCspring-webmvcMyBatismybatisMyBatis-Spring整合mybatis-spring关键数据库驱动mysql-connector-java数据库连接池推荐使用com.alibaba.druid它功能强大自带监控。JSP相关javax.servlet-api,javax.servlet.jsp-api,jstl日志log4j-core,log4j-api(Log4j2) 或slf4jlogbackJSON处理jackson-databind用于Controller返回JSON数据给前端测试junit配置web.xml这是Web应用的入口。你需要在这里配置Spring的监听器ContextLoaderListener用于加载Spring的根容器主要管理Service、Dao等Bean。Spring MVC的前端控制器DispatcherServlet用于拦截所有请求加载Spring MVC的容器主要管理Controller等Bean。字符编码过滤器解决中文乱码问题必须配置。!-- 字符编码过滤器 -- 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 init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping !-- Spring根容器配置 -- 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 !-- Spring MVC前端控制器 -- servlet servlet-namedispatcherServlet/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-valu /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcherServlet/servlet-name url-pattern//url-pattern /servlet-mapping配置Spring和MyBatis这是整合的核心。applicationContext.xml配置数据源Druid、事务管理器、以及扫描Service和Dao层的Bean。spring-mvc.xml配置扫描Controller、视图解析器告诉Spring MVC JSP文件在哪里、静态资源处理、开启注解驱动。mybatis-config.xml配置MyBatis的全局设置如驼峰命名映射、别名等。但很多配置可以合并到Spring配置中。关键整合步骤在applicationContext.xml中配置SqlSessionFactoryBean将MyBatis的SqlSessionFactory交给Spring管理并注入数据源和Mapper XML文件的位置。再配置MapperScannerConfigurer让它自动扫描Dao接口并为其创建Spring Bean。这个过程就像搭积木每一步都是下一层的基础。配置完成后启动Tomcat如果控制台没有报错并能看到Spring和MyBatis初始化的日志那么恭喜你环境搭建成功了。4. 核心功能实现与代码详解4.1 实体类与Mapper层实现一切从定义数据开始。首先创建Customer实体类属性对应数据库字段并生成getter/setter方法。这里可以使用Lombok插件简化代码。然后创建Dao层接口CustomerMapper.java。这里只定义方法不写实现。public interface CustomerMapper { int insert(Customer customer); int deleteById(Integer id); int update(Customer customer); Customer selectById(Integer id); ListCustomer selectAll(); ListCustomer selectByCondition(Param(name) String name, Param(phone) String phone); }真正的SQL写在对应的XML文件CustomerMapper.xml中放在resources/mappers目录下。?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.yourcompany.crm.dao.CustomerMapper resultMap idBaseResultMap typecom.yourcompany.crm.entity.Customer id columnid propertyid/ result columnname propertyname/ result columngender propertygender/ result columnphone propertyphone/ !-- 其他字段映射 -- /resultMap insert idinsert parameterTypeCustomer useGeneratedKeystrue keyPropertyid INSERT INTO t_customer (name, gender, phone, email, ...) VALUES (#{name}, #{gender}, #{phone}, #{email}, ...) /insert select idselectByCondition resultMapBaseResultMap SELECT * FROM t_customer where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testphone ! null and phone ! AND phone #{phone} /if /where /select /mapper这里有个非常重要的技巧where标签和if标签的使用。where标签会智能地处理WHERE关键字如果里面所有的if条件都不成立它不会生成WHERE避免了WHERE后面直接跟AND的语法错误。if标签实现了动态SQL根据传入的参数决定是否拼接某段SQL。这使得一个方法可以应对多种查询条件组合非常灵活。4.2 Service业务逻辑层封装Service层是业务逻辑的核心。我们先定义接口CustomerService再写实现类CustomerServiceImpl。这样做是为了面向接口编程提高扩展性。Service // Spring注解声明这是一个Service Bean public class CustomerServiceImpl implements CustomerService { Autowired // Spring自动注入Mapper Bean private CustomerMapper customerMapper; Override public boolean addCustomer(Customer customer) { // 业务校验手机号格式、邮箱格式、非空检查等 if (customer.getPhone() null || !isValidPhone(customer.getPhone())) { throw new IllegalArgumentException(手机号格式错误); } // 检查手机号是否已存在 Customer exist customerMapper.selectByPhone(customer.getPhone()); if (exist ! null) { throw new RuntimeException(手机号已存在); } // 设置创建时间等默认值 customer.setCreateTime(new Date()); customer.setUpdateTime(new Date()); // 调用Mapper插入数据 return customerMapper.insert(customer) 0; } Override Transactional // 声明式事务注解如果方法内发生异常所有数据库操作回滚 public boolean updateCustomer(Customer customer) { // 更新前先检查客户是否存在 Customer old customerMapper.selectById(customer.getId()); if (old null) { throw new RuntimeException(客户不存在); } // 如果手机号有修改需要检查新手机号是否已被他人使用 if (!old.getPhone().equals(customer.getPhone())) { Customer exist customerMapper.selectByPhone(customer.getPhone()); if (exist ! null) { throw new RuntimeException(新手机号已被其他客户使用); } } customer.setUpdateTime(new Date()); return customerMapper.update(customer) 0; } // ... 其他方法 }事务管理注意Transactional注解。在更新操作中我们可能先查询再更新这应该是一个原子操作。加上这个注解后Spring会为这个方法开启一个数据库事务如果方法执行成功则提交如果抛出运行时异常则回滚保证了数据的一致性。4.3 Controller层与前后端交互Controller层接收HTTP请求调用Service然后返回结果。它像是一个服务员接收顾客前端的点单请求交给后厨Service处理再把做好的菜响应端回去。Controller // 声明这是一个Controller RequestMapping(/customer) // 类级别的映射所有方法路径前都会加上/customer public class CustomerController { Autowired private CustomerService customerService; // 跳转到客户列表页 GetMapping(/list) public String listPage() { return customer/list; // 对应 /WEB-INF/jsp/customer/list.jsp } // 获取客户列表数据JSON格式供前端表格异步加载 GetMapping(/api/list) ResponseBody // 表示返回的是JSON数据而不是视图名 public MapString, Object getCustomerList( RequestParam(value pageNum, defaultValue 1) Integer pageNum, RequestParam(value pageSize, defaultValue 10) Integer pageSize, RequestParam(value name, required false) String name) { // 调用Service进行分页查询 PageHelper.startPage(pageNum, pageSize); // 使用PageHelper物理分页 ListCustomer list customerService.getCustomersByName(name); PageInfoCustomer pageInfo new PageInfo(list); MapString, Object result new HashMap(); result.put(success, true); result.put(data, pageInfo.getList()); // 当前页数据 result.put(total, pageInfo.getTotal()); // 总记录数 return result; } // 新增客户 PostMapping(/api/add) ResponseBody public MapString, Object addCustomer(RequestBody Customer customer) { // RequestBody接收JSON格式的请求体 MapString, Object result new HashMap(); try { customerService.addCustomer(customer); result.put(success, true); result.put(message, 添加成功); } catch (Exception e) { result.put(success, false); result.put(message, 添加失败: e.getMessage()); } return result; } }关键点解析GetMapping,PostMapping这是Spring MVC的注解分别映射HTTP的GET和POST请求。RESTful风格建议获取数据用GET新增用POST更新用PUT删除用DELETE。ResponseBody这个注解至关重要。它告诉Spring MVC方法的返回值不是视图名而是要直接写入HTTP响应体的数据通常转换为JSON。配合RestController注解等于ControllerResponseBody使用更简洁。RequestBody用于将前端发送的JSON字符串自动绑定到后端的Java对象上。分页我们使用了PageHelper这个国产开源分页插件。它通过拦截MyBatis的SQL自动拼接分页语句LIMIT非常方便。PageInfo对象包含了分页的所有信息当前页、总页数、数据列表等。4.4 前端页面与数据绑定前端我们使用简单的JSP Bootstrap jQuery。JSP负责页面骨架Bootstrap负责样式jQuery负责发起Ajax请求与后端交互。在list.jsp中我们会有一个表格来展示客户列表。table idcustomerTable classtable table-hover theadtrthID/thth姓名/thth手机号/thth操作/th/tr/thead tbody!-- 数据由JS动态加载 --/tbody /table nav idpagination!-- 分页组件 --/nav使用jQuery的Ajax来加载数据function loadCustomerData(pageNum, name) { $.ajax({ url: ${pageContext.request.contextPath}/customer/api/list, type: GET, data: {pageNum: pageNum, pageSize: 10, name: name}, dataType: json, success: function(result) { if (result.success) { // 1. 清空表格 $(#customerTable tbody).empty(); // 2. 遍历数据拼接HTML行 $.each(result.data, function(index, customer) { var row tr td customer.id /td td customer.name /td td customer.phone /td td button classbtn btn-sm btn-primary onclickeditCustomer( customer.id )编辑/button button classbtn btn-sm btn-danger onclickdeleteCustomer( customer.id )删除/button /td /tr; $(#customerTable tbody).append(row); }); // 3. 渲染分页组件 renderPagination(result.total, pageNum); } } }); }前后端分离的雏形虽然我们用了JSP但数据是通过Ajax异步获取的JSON页面渲染在浏览器端完成。这已经是前后端分离的一种简单形式。这样做的好处是页面无需刷新用户体验好后端Controller只负责提供数据接口职责清晰。5. 项目部署、测试与常见问题排查5.1 项目打包与部署开发完成后需要将项目部署到服务器或交给老师验收。Maven的打包命令是mvn clean package。对于Web项目会生成一个项目名.war文件。本地Tomcat部署将生成的.war文件复制到Tomcat的webapps目录下启动Tomcat它会自动解压并部署。访问http://localhost:8080/你的项目名/即可。外置Tomcat部署IDEA在IDEA的Run/Debug Configuration中配置Tomcat指定Deployment为Artifact选择你项目生成的war包。这样可以在IDEA中直接启动和调试。打可执行Jar包Spring Boot方式如果你的项目后期想改为Spring Boot可以使用spring-boot-maven-plugin打包成一个包含内嵌Tomcat的、可直接通过java -jar运行的Jar包部署极其方便。但对于纯SSM项目通常还是打War包。5.2 功能测试要点测试不能只靠眼睛看要有计划。单元测试使用JUnit对Service层的方法进行测试。确保每个业务逻辑如新增时的校验、更新时的检查都正确。可以使用RunWith(SpringJUnit4ClassRunner.class)和ContextConfiguration加载Spring配置然后Autowired注入Service进行测试。接口测试使用Postman或浏览器直接访问Controller的API接口检查返回的JSON数据是否正确。重点测试边界情况传入空值、超长字符串、不存在的ID等。界面集成测试在浏览器中手动操作覆盖所有功能点增、删、改、查、分页、条件搜索。检查页面交互是否流畅错误提示是否友好。数据库验证在Navicat里直接查看数据表确认操作后的数据状态是否符合预期特别是时间戳字段。5.3 常见问题与解决方案实录以下是我在开发和教学过程中学生遇到最高频的几个问题问题现象可能原因排查步骤与解决方案启动Tomcat时报ClassNotFoundException或NoClassDefFoundError1. 依赖的Jar包没有正确引入。2. Jar包版本冲突。3. Tomcat提供的Servlet-api与项目中的冲突。1. 检查pom.xml依赖运行mvn clean compile看是否报错。2. 在IDEA中打开Project Structure - Modules - Dependencies查看是否有红色波浪线。3. 将servlet-api的依赖范围设为provided表示该包由Tomcat容器提供。页面中文乱码1. 数据库连接字符集不是UTF-8。2. 请求/响应编码未设置。3. JSP页面编码未设置。1. 检查数据库、表、字段的字符集是否为utf8mb4。2. 确认web.xml中配置了字符编码过滤器且forceEncoding为true。3. 在JSP页面头部添加% page contentTypetext/html;charsetUTF-8 languagejava %。4. 在Spring MVC配置中设置mvc:annotation-driven并配置消息转换器。404错误页面找不到1. 请求路径错误。2.DispatcherServlet的url-pattern配置问题。3. Controller方法未被扫描到。1. 检查浏览器地址栏URL是否与RequestMapping的值匹配。2. 检查web.xml中DispatcherServlet的url-pattern通常为/。3. 检查spring-mvc.xml中组件扫描的包路径是否包含了Controller类。500错误服务器内部错误1. 空指针异常NPE。2. SQL异常。3. 类型转换错误。1.看控制台日志Tomcat的catalina.out或IDEA的控制台会打印详细的异常堆栈信息这是定位问题的关键。2. 检查Service或Controller中自动注入Autowired的对象是否为null。3. 检查MyBatis的SQL映射文件参数#{}中的属性名是否与实体类一致。Autowired注入失败对象为null1. 该类没有被Spring管理缺少Component,Service等注解。2. 包路径不在Spring的扫描范围内。3. 存在多个同类型的Bean。1. 确保被注入的类如CustomerServiceImpl上有Service注解。2. 检查applicationContext.xml中的context:component-scan的base-package是否包含了该类的包。3. 使用Qualifier注解指定Bean的名称。MyBatis查询结果为空或字段映射失败1. SQL语句有误。2. 数据库连接错误连到了别的库或表。3. 实体类字段名与数据库列名不一致未开启驼峰映射。1. 将MyBatis的日志级别设为DEBUG在控制台查看实际执行的SQL。2. 检查jdbc.properties中的数据库名、表名。3. 在MyBatis配置中设置setting namemapUnderscoreToCamelCase valuetrue/开启自动驼峰命名映射user_name-userName。事务Transactional不生效1. 方法不是public的。2. 异常类型不是运行时异常RuntimeException。3. 在同一个类内部方法调用走了代理对象。1. 确保事务方法为public。2. 默认只回滚运行时异常和Error。可配置Transactional(rollbackFor Exception.class)回滚所有异常。3. 避免在同一个Service类中一个非事务方法调用另一个事务方法。一个典型的排错流程遇到问题 - 1. 看浏览器控制台F12的Network和Console标签看请求是否发出、状态码、响应内容。 - 2. 看服务器IDEA控制台日志找异常堆栈。 - 3. 根据异常信息定位到具体的代码行。 - 4. 使用Debug模式打断点逐步执行观察变量状态。5.4 项目扩展与优化建议如果你想让你的毕设脱颖而出可以尝试以下扩展方向这会在答辩时给你加分加入权限控制使用Spring Security或Shiro框架实现简单的登录、角色管理如管理员和普通用户、权限拦截。让系统更完整。实现文件上传允许为客户上传头像或合同附件。Spring MVC对文件上传有很好的支持结合OSS对象存储会更专业。添加数据导出功能使用Apache POI或EasyExcel库将客户列表导出为Excel文件。引入缓存对于不常变动的数据如客户类型字典可以使用Redis进行缓存减轻数据库压力提升查询速度。前端框架升级将JSP替换为更现代化的前端框架如Vue.js或React实现真正的前后端分离。后端Controller只提供RESTful API。改为Spring Boot尝试将项目迁移到Spring Boot体验一下“约定大于配置”的便捷对比SSM和Spring Boot的异同这本身就是一个很好的学习点。做这个项目最重要的不是把代码敲完而是在这个过程中理解每一个配置的意义每一行代码的作用遇到问题并解决它。当你看到浏览器上成功显示出从数据库查出来的客户列表时那种成就感就是学习编程最大的动力。这个项目做完Java Web开发的大门你就已经推开一半了。本文还有配套的精品资源点击获取