尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SSM学生管理系统搭建实战:JSP+Spring+MyBatis+MySQL全流程解析

SSM学生管理系统搭建实战:JSP+Spring+MyBatis+MySQL全流程解析 简介基于JSPMySQLSpringMyBatis的SSM学生管理系统压缩包面向JavaWeb课程设计及入门开发者覆盖管理员登录、学生管理、成绩管理、教师信息管理、课程管理、学院专业与班级管理、用户管理等典型模块。压缩包共95个文件以53个Java源文件、15个JSP页面为主辅以MyBatis映射XML、Spring配置、前端JS/CSS以及少量图片资源整体大小仅871KB结构紧凑便于排查与学习。该资源已有4058人学习下载适合正在完成学生管理类课题或希望理解SSM整合流程的读者。解压后可直接导入Eclipse、MyEclipse或IDEA配合JDK1.8与Tomcat9实现运行从中可获取完整的SSM分层代码、数据库交互逻辑、登录鉴权与增删改查实现思路对毕业设计或课设二次开发有较高参考价值。1. 这套技术栈为什么至今还有人用说实话看到“jspmysqlSpringmybatis的SSM学生管理系统”这个标题很多新生代程序员的第一反应是“这都什么年代了还在用JSP”。可实际上我现在接到的外包和课程设计需求里这类题目依然占了相当比例。原因其实不复杂它恰好是一个能让人把“前端页面渲染、后端业务逻辑、数据库持久化”整条链路一次跑通的组合而且大部分高校的Java Web课程还在用它做教学载体。这套组合的核心价值在于“分工清晰”JSP负责把数据渲染成HTML页面Spring负责管理对象和事务MyBatis负责把Java对象映射成SQL操作MySQL负责真正落盘存储。对于学生信息管理这类典型CRUD场景——增删改查、分页搜索、批量导入、登录权限——它们的配合度非常高几乎没有多余的概念负担。用今天的话说它就是那个年代的“低代码方案”。我也知道很多人会杠“Spring Boot不香吗”香但它掩盖了太多细节。SSM反而逼着你手动处理web.xml、Spring容器配置、MyBatis的Mapper绑定这些“脏活”恰恰是理解Java Web运行原理的关键。所以这篇博客会把我的实际搭建过程拆开讲尤其是那些在教程里一笔带过、但真正让你卡住半个下午的细节。如果你是正在做课程设计或毕业设计的学生或者拿到一个老项目需要快速上手维护这篇内容应该能让你少走很多弯路。我会按照我自己的实操顺序来写尽量把“为什么这么做”也交代清楚。提示JSP技术虽老但其背后“模板渲染Servlet容器”的思想至今仍在Thymeleaf、FreeMarker等方案中延续。学到手的原理不会白费。2. 前置准备JDK版本、IDE与MySQL安装的三个坑2.1 版本搭配是成败的一半这是整个项目里最容易踩雷的环节。很多人一上来就装最新版JDK和MySQL结果连Maven依赖都解析不过去还以为是代码写错了。我的建议搭配是这样的JDK 1.8不要用11或17除非你想练手兼容性改造Maven 3.6.xTomcat 8.5或9.0MySQL 5.7经典版稳定性好8.0也没问题但注意驱动名和时区配置Eclipse或IDEA个人推荐IDEA下文会说为什么这套组合的兼容性损耗最小网上能查到的报错解决方案也最多。JDK版本过高时JSP编译会遇到“模块访问限制”之类的报错处理起来虽然不难但对刚入门的人非常劝退。2.2 IDE选择不是说Eclipse不行网上大量SSM教程用Eclipse演示因为很多高校机房预装的就是它。但如果你是自己电脑我强烈建议用IDEA。区别在于IDEA对Maven的支持是“开箱即用”的Eclipse经常要手动配置Maven仓库和导入插件。IDEA的智能提示对MyBatis的Mapper接口和XML映射文件有更好的支持能直接跳转。最关键的一点IDEA部署Web项目时Artifacts配置比Eclipse直观得多减少很多“明明代码没错但404”的玄学问题。当然如果你已经习惯Eclipse坚持用也可以后面排错的思路都一样差的只是操作路径。2.3 MySQL安装时最容易忽略的细节MySQL安装本身不算难但有两个细节我几乎每次帮人远程调试都会遇到第一端口冲突。很多人机器上已经装了旧版MySQL或者MariaDB新装的MySQL 5.7默认端口也是3306启动会直接失败。解决办法是安装时把端口改成3307或者在服务管理器里停掉旧服务。如果你只是想先跑通项目改项目里的数据库连接端口更省事。第二MySQL 8.0以上默认认证插件是caching_sha2_password而老版本JDBC驱动比如5.1.49之前的不兼容会报“Public Key Retrieval is not allowed”。如果你用MySQL 8.0我建议连接URL里加一个参数jdbc:mysql://localhost:3306/ssm_student?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueserverTimezone也是容易漏的不加的话JDBC驱动会报时区错误。我见过有人在这上面卡了快一下午最后发现只是少写了一个参数。数据库装好之后记得创建一个专门给项目用的账号不要直接用root。这倒不是安全洁癖而是从开发习惯上让你意识到数据库权限边界的问题。建账号的语句CREATE USER ssm_userlocalhost IDENTIFIED BY yourpassword; GRANT ALL PRIVILEGES ON ssm_student.* TO ssm_userlocalhost; FLUSH PRIVILEGES;这样即便后面你的SQL写炸了影响的也只是这一个库不至于把整个MySQL环境搞坏。3. 从零搭建项目骨架Maven配置与启动链路3.1 用Maven创建Web工程IDEA里新建一个“Maven Archetype”项目选择maven-archetype-webapp骨架。选这个而不是直接建普通Project是为了拿到符合Web规范的目录结构src/main/java、src/main/resources、src/main/webapp。项目创建后第一件事是补全目录结构src/main/java src/main/resources src/main/webapp/WEB-INF如果你发现src/main/java这个目录在IDEA里是灰色的或者不存在手动新建就行然后右键“Mark Directory as Sources Root”让IDEA认识它。3.2 pom.xml依赖清单pom.xml是整个项目的地基。我通常这样配置properties spring.version5.3.23/spring.version mybatis.version3.5.10/mybatis.version mysql.version5.1.49/mysql.version /properties dependencies !-- Spring核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis整合 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version${mysql.version}/version /dependency !-- 连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.6/version /dependency !-- Servlet相关 -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- 日志 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-api/artifactId version1.7.36/version /dependency dependency groupIdch.qos.logback/groupId artifactIdlogback-classic/artifactId version1.2.10/version /dependency /dependencies注意几点javax.servlet-api的scope一定是provided因为Tomcat自带Servlet容器实现如果打包进去会冲突。MyBatis和Spring整合时mybatis-spring版本需要和Spring大版本匹配我用的组合已经验证过没问题。日志我用的是Logback比log4j配置简洁也没有log4j2那个远程代码执行漏洞的历史包袱。3.3 配置文件从web.xml到Spring容器这部分是SSM项目“搭得起来但跑不起来”最常见的重灾区。我按启动顺序来梳理。首先是web.xml它定义了整个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/springmvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping这里有一个很多新手都会懵的点为什么要配两个Spring容器一个是ContextLoaderListener加载的根容器负责管理Service、DAO这些业务层组件另一个是DispatcherServlet加载的子容器只负责Controller。子容器能看到父容器的Bean反过来不行。这样分层是为了职责清晰——控制器只管接收请求和返回视图别去碰数据源。然后是spring/applicationContext.xml这里配置数据源、事务管理器并通过扫描包让Spring管理Service和DAOcontext:component-scan base-packagecom.ssm.service / context:component-scan base-packagecom.ssm.dao / bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/ssm_student?useSSLfalseamp;characterEncodingutf8/ property nameusername valuessm_user/ property namepassword valueyourpassword/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.ssm.dao/ /beanspringmvc.xml则只扫描Controller包context:component-scan base-packagecom.ssm.controller / mvc: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之所以把JSP放在/WEB-INF/jsp/下面是因为WEB-INF目录下的资源客户端不能直接通过URL访问必须经过Controller转发才能渲染这是一个默默提升安全性的好习惯——用户没法绕过权限控制直接看页面。4. 数据层设计与MyBatis映射从建表到批量插入4.1 学生表的字段设计思路学生信息管理系统的核心表不用设计得太复杂但字段类型上有些讲究。我习惯这样建CREATE TABLE student ( id INT AUTO_INCREMENT PRIMARY KEY, student_no VARCHAR(20) NOT NULL UNIQUE, name VARCHAR(50) NOT NULL, gender TINYINT DEFAULT 1 COMMENT 1-男 2-女, age TINYINT, class_name VARCHAR(100), phone VARCHAR(20), email VARCHAR(100), create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个设计细节学号student_no单独加唯一索引业务上不允许两个学生共用学号这比在代码里判断快且更可靠。性别用TINYINT而不是CHAR(1)因为JSP页面上的性别往往由下拉框提交数字类型在前后端转换上少一步隐式处理。create_time和update_time用数据库的默认值管理不要写在Java代码里。尤其在多人协作时这一条能避免“为什么我插入的数据没时间”这种问题。字符集统一用utf8mb4不是utf8。utf8mb4才完整支持emoji和生僻字虽然学生管理场景很少用到但“不炸”比“省那几KB空间”重要。4.2 Mapper XML的写法动态SQL价值全在这里MyBatis最划算的功能就是动态SQL。学生管理系统的查询条件往往是组合的——可能只按班级查可能只按名字模糊查也可能是名字班级一起查。每个组合都写一条SQL你会疯掉的用动态SQL一行搞定select idsearchStudents resultTypecom.ssm.entity.Student SELECT * FROM student where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testclassName ! null and className ! AND class_name #{className} /if if testgender ! null AND gender #{gender} /if /where ORDER BY id DESC /select注意这里用where标签而不是手动写WHERE 11。where会自动处理“第一个条件前面的AND要不要去掉”的问题比WHERE 11优雅也避免了一个坑如果所有条件都为空where会输出空字符串而不会生成一个孤零零的WHERE。4.3 批量插入JSP页面表单循环提交的大坑学生管理系统免不了“批量录入”功能常见做法是Excel导入或者页面表格多行填写。如果你用循环去调用单条insert数据量一上来性能立刻拉胯。MyBatis支持批量插入的foreach语法insert idbatchInsert INSERT INTO student (student_no, name, gender, age, class_name, phone, email) VALUES foreach collectionlist itemitem separator, (#{item.studentNo}, #{item.name}, #{item.gender}, #{item.age}, #{item.className}, #{item.phone}, #{item.email}) /foreach /insert有一条非常重要的MySQL限制max_allowed_packet。默认情况下如果超过1M或4M批量插入会直接抛异常。你可以在MySQL配置文件中调大[mysqld] max_allowed_packet64M我建议在开发环境直接调大避免测试数据量稍微上来就莫名报错。另外虽然foreach批量插入很爽但一次插入条数建议控制在500条以内超过了就分批否则SQL文本太长MySQL解析和网络传输都会变慢。4.4 分页查询手写LIMIT还是用PageHelper学生列表页几乎必然要分页。方案有两种手写LIMIT配合前端传参或者用PageHelper插件。PageHelper的接入成本极低只加一个依赖再在MyBatis配置里加一行bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean ... property nameplugins array bean classcom.github.pagehelper.PageInterceptor property nameproperties value helperDialectmysql reasonabletrue /value /property /bean /array /property /bean代码里这样用PageHelper.startPage(pageNum, pageSize); ListStudent list studentMapper.searchStudents(student); PageInfoStudent pageInfo new PageInfo(list);PageHelper.startPage之后的第一次查询会自动被拦截拼上LIMITPageInfo里包含了总条数、总页数、当前页这些分页需要的所有信息。要注意的是startPage必须紧跟查询语句中间不能插别的方法调用否则分页数据会错乱。这个坑我踩过查出来的总数莫名变成10后来才发现是前面多打了一行日志。5. 控制层与JSP交互ModelAndView、RequestParam和EL表达式5.1 Controller的标准写法控制层是前后端沟通的翻译官。我的Controller通常长这样Controller RequestMapping(/student) public class StudentController { Autowired private StudentService studentService; RequestMapping(/list) public String list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, Student query, Model model) { PageInfoStudent pageInfo studentService.searchStudents(pageNum, pageSize, query); model.addAttribute(pageInfo, pageInfo); model.addAttribute(query, query); return student/list; } RequestMapping(/toAdd) public String toAdd() { return student/add; } RequestMapping(/doAdd) public String doAdd(Validated Student student, BindingResult result, RedirectAttributes attr) { if (result.hasErrors()) { attr.addFlashAttribute(errorMsg, 参数校验失败); return redirect:/student/toAdd; } studentService.addStudent(student); return redirect:/student/list; } }几个实操要点接收查询参数时不要一个个写RequestParam直接把Student对象作为参数Spring MVC会自动把同名请求参数绑定到对象属性上省掉一堆getParameter代码。表单提交后一定要用“重定向”redirect:不要直接返回视图。否则刷新页面就会重复提交表单产生幽灵数据。RedirectAttributes.addFlashAttribute可以在重定向后带一次性的提示信息这个信息只能在下一个页面显示一次刷新就消失正好用来做“新增成功”这类提示。5.2 JSP页面里的EL表达式与JSTLJSP页面里遍历数据用JSTL的c:forEach判断用c:if这是最高频的两种标签。我用一个最小示例说明table classtable table-bordered thead tr th学号/th th姓名/th th性别/th th班级/th th操作/th /tr /thead tbody c:forEach items${pageInfo.list} varstu tr td${stu.studentNo}/td td${stu.name}/td td c:choose c:when test${stu.gender 1}男/c:when c:otherwise女/c:otherwise /c:choose /td td${stu.className}/td td a href${pageContext.request.contextPath}/student/toEdit?id${stu.id}编辑/a a href${pageContext.request.contextPath}/student/delete?id${stu.id} onclickreturn confirm(确定删除该学生吗)删除/a /td /tr /c:forEach /tbody /table两个容易忽略的点items属性里写的是${pageInfo.list}不是${pageInfo}。PageInfo.getList()才是具体数据列表直接把PageInfo传到c:forEach里会报遍历错误。路径拼接必须用${pageContext.request.contextPath}不要写死/ssm_student。一旦以后改了项目名写死的路径全部失效而用这个自带的上下文路径会自动跟随项目名变化。5.3 JSP脚本片段为什么会成为反面教材JSP文件里有两种写Java代码的方式% ... %脚本片段以及% ... %表达式输出。我见过很多课程设计代码Controller返回的Model放到一边直接在JSP里用%连接数据库、跑查询、写逻辑。页面能跑但在工程上非常危险JSP的职责是“展示”一旦混入业务逻辑后期维护变成噩梦。脚本片段里的Java代码无法在编译期发现大部分错误运行时才暴露出问题后排查成本很高。EL表达式和JSTL标签已经能覆盖绝大多数展示需求完全没有必要在JSP里手写Java。用EL和JSTL替换脚本片段阅读体验是“面向页面读逻辑”而不是“在一堆Java代码里猜HTML结构”。6. 前端页面与浏览器交互审批流、文件路径这些热搜词聊两句6.1 学生管理系统的前端只需做到“够用”从热搜词里看到“jsp代码谷歌浏览器获取保存文件路径”“jsp项目中前端使用jsjquery如何实现设置审批流”这些搜索我会怀疑大家是不是把学生管理系统和办公审批系统搞混了。不过从技术角度这些需求本质上是一个问题JSP页面里如何用JS和jQuery跟前端做异步交互。核心思路是在JSP页面中引入jQuery库然后通过AJAX向后端Controller发请求后端返回JSON格式数据前端用JavaScript解析并动态更新页面。我用一个“通过班级动态加载学生列表”的例子来说明script src${pageContext.request.contextPath}/static/js/jquery-3.6.0.min.js/script script $(function () { $(#className).change(function () { var className $(this).val(); $.ajax({ url: ${pageContext.request.contextPath}/student/loadByClass, type: GET, data: {className: className}, dataType: json, success: function (data) { var options ; for (var i 0; i data.length; i) { options option value data[i].id data[i].name /option; } $(#studentSelect).html(options); } }); }); }); /scriptController返回JSON的写法很简单RequestMapping(/loadByClass) ResponseBody public ListStudent loadByClass(String className) { return studentService.findByClassName(className); }有了ResponseBodySpring MVC会用Jackson把返回对象自动转成JSON。前提是pom.xml里加了Jackson依赖而且Spring配置文件里开启了mvc:annotation-driven /。别小看这个标签很多人Spring MVC跑不起来JSON接口就是它没开。6.2 文件上传和路径处理的注意点热搜词里“谷歌浏览器获取保存文件路径”这类需求在浏览器安全模型下是做不到的出于安全考虑Chrome不会向JS暴露用户本地的绝对路径只提供文件名和文件对象本身。所以如果你要做学生照片上传功能正确的姿势是前端用input typefile传给后端MultipartFile对象。后端把文件保存到你配置的服务器目录比如/upload/avatar/存进数据库的是相对路径或URL。页面显示时拼接完整URL而不是直接引用本地路径。也就是说浏览器端的“路径幻觉”在Web开发里尤其要警惕。你拿到C:\fakepath\photo.jpg那不是路径只是一个浏览器刻意替换过的展示字符串。6.3 JSP页面杀毒软件误报的问题热搜词里有一条“冰蝎jsp免杀”我要专门提一下。冰蝎是一种WebShell管理工具它的JSP木马会被杀毒软件和Web应用防火墙扫描识别。我不讨论免杀技术但想强调两点如果你在自己的学生管理系统里写JSP用了Runtime.exec()之类的代码请自查它的用途是否正常。杀毒软件对JSP文件的静态扫描确实会误伤一些常见的动态执行代码这不是你的错但也说明“在JSP里写执行命令相关代码”这件事本身就非常危险。从安全角度看JSP页面根本不应该承担任何操作系统命令执行、反射调用这类职责。把这些能力请出你的项目杀毒软件不会误报攻击面也小了很多。7. 事务控制与性能调优日志、SQL打印和缓存7.1 事务边界到底划在哪里一个学生管理系统里“新增学生”和“新增学生添加一条操作日志”这两个场景的事务边界就不一样。前者单条SQL天然事务后者必须让两个操作成为一个原子操作——学生没插入成功日志也不要写。SSM项目里事务推荐用声明式事务在Spring配置文件里这样开启tx:annotation-driven transaction-managertransactionManager / bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean然后在Service实现类上打注解Service public class StudentServiceImpl implements StudentService { Autowired private StudentMapper studentMapper; Autowired private OperationLogMapper operationLogMapper; Transactional(rollbackFor Exception.class) Override public void addStudentWithLog(Student student, String operator) { studentMapper.insert(student); OperationLog log new OperationLog(); log.setOperation(新增学生); log.setOperator(operator); operationLogMapper.insert(log); } }rollbackFor Exception.class必须写。Spring默认只回滚RuntimeException如果你抛的是自定义CheckedException不加这个参数事务不会回滚数据就会产生半完成状态。这个坑极其隐蔽因为项目在正常流程下测不出来只有特定异常才会触发。7.2 在控制台打印完整SQL语句MyBatis默认不打印SQL参数排查问题时等于瞎猜。有两种方式开启日志方式一在logback配置文件里为mapper包单独设置DEBUG级别logger namecom.ssm.dao levelDEBUG /方式二在MyBatis全局配置里configuration settings setting namelogImpl valueSTDOUT_LOGGING/ /settings /configuration推荐方式一因为只输出Mapper层SQL相关日志不会像STDOUT_LOGGING那样把整个会话过程都刷出来日志量太大反而看不清关键信息。7.3 本地缓存还是分布式缓存学生管理系统通常是单机应用不建议为了“看起来厉害”引入Redis。MyBatis自带的二级缓存对这个体量的项目足够了。在Mapper XML里加一行cache evictionLRU flushInterval60000 size512 readOnlytrue/这表示使用最近最少使用淘汰策略每60秒刷新一次最多缓存512个对象只读。要注意的是只要执行了该Mapper下的任何insert/update/delete操作MyBatis会自动清空该缓存区域保证数据一致性。所以“学生被删除了但列表页还是能看到”这种问题在二级缓存下不太可能出现。如果将来数据量真的上去了优先考虑升级的是MySQL索引和SQL优化而不是上缓存。给学生表建上对应索引后万级数据量下的查询就是毫秒级响应学生管理系统这个场景里根本没有缓存什么事。8. 部署调试中的拦路虎404、ClassNotFound和数据库乱码8.1 一启动就404的排查思路404是SSM新人最容易碰到的错误。我的排查顺序固定如下看Tomcat启动日志有没有“Exception”或“Error”。有异常先解决异常。看控制台是否有“Jar does not exist”或者“ClassNotFound”之类的Maven依赖问题有则右键项目Maven → Reimport。确认访问URL和Controller的RequestMapping路径是否完全一致包括大小写、斜杠。确认web.xml里DispatcherServlet的url-pattern如果你配的是/那么静态资源也会被DispatcherServlet拦截需要配合mvc:resources放行静态文件。最后看InternalResourceViewResolver的prefix/suffix配置和JSP文件是不是真的在/WEB-INF/jsp/下。这五步走完80%的404都能解决。8.2 数据库乱码的根本解法乱码表面上是“中文变成了问号”根子上是“字符集在链路各环节不一致”。Java Web的字符集链路是页面提交 → Servlet接收 → JDBC传输 → MySQL存储 → 查询返回 → 页面渲染。任何一个环节断了全断。全面解决方案就三条JSP页面顶部确保有% page contentTypetext/html;charsetUTF-8 languagejava %。连接URL里加上characterEncodingutf8这个参数传给MySQL驱动后JDBC会自动以UTF-8编码交互。表结构本身是utf8mb4。如果数据已经插成乱码先修正配置再把已有的脏数据重新插一遍别想着在已有乱码数据上“恢复”——源信息丢了恢复不出来。8.3 老项目换Tomcat版本时常见的兼容性报错有次我接手一个老SSM项目原本部署在Tomcat 7换成Tomcat 9后启动直接报错“Invalid byte tag in constant pool: 15”。原因很简单代码里用了高版本JDK编译的class文件而Tomcat 7默认运行在低版本JDK上字节码版本不兼容。解决方案也很直接统一整个链路的JDK版本。要么把项目编译级别调回1.7或1.8要么把Tomcat也换成对应支持新JDK的版本。后来我统一用JDK 1.8 Tomcat 8.5这个组合迄今没再出过兼容性幺蛾子。9. 一个完整的“新增学生”流程串起来看光讲理论容易飘我最后把前面所有内容串成一个完整的链路从你在页面上点击“保存”开始看看代码都经历了什么。用户提交表单后浏览器发送POST请求到/student/doAdd请求头带上表单字段studentNo、name、gender等。Tomcat根据web.xml里的映射把请求交给DispatcherServletDispatcherServlet通过HandlerMapping找到StudentController.doAdd方法Spring把request参数自动绑定到Student对象。doAdd调用Service层Service开启事务调用Mapper接口MyBatis通过动态代理找到对应的Mapper XML里的SQL填入参数提交给MySQL执行。返回后Controller把结果转发到“重定向”地址浏览器拿到302响应再发起GET请求访问/student/list。此时Controller重新查一次列表渲染出JSP页面最终你看到新增的学生出现在第一行。这整条链路里每一环的知识点都是独立的表单参数绑定对应Spring MVC的数据绑定机制Service事务对应Spring的声明式事务设计Mapper动态SQL对应MyBatis的代理与反射JSP渲染对应Servlet和模板引擎的关系学SSM如果能把这套链路的每一环都讲到“能闭上眼画出来”的程度换到任何其他框架都不会太慌。也许接口路径变了也许注解不同了但“请求到响应”的骨架没变过。我个人在带新人和做项目维护时最常用的调试动作不是加断点而是先看控制台有没有SQL输出、再看参数有没有传对。这两步能过滤掉大约七成的问题。如果在搭建过程中你卡在某个具体报错上不妨把关键日志贴出来咱们一起看。这本身就是学Java Web最有效的路径——一个坑一个坑踩过去踩完就都懂了。本文还有配套的精品资源点击获取
返回列表