ARTICLE DETAIL

资讯详情

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

SSM学生信息管理系统源码拆解:从配置到原理的Java Web指南

SSM学生信息管理系统源码拆解:从配置到原理的Java Web指南 简介Java Web开发中SSMSpringSpringMVCMyBatis作为经典技术栈深刻影响着后端工程师对框架协作与底层机制的理解。其核心原理在于Spring管理业务对象SpringMVC处理请求分发MyBatis负责SQL映射三者通过配置文件协同工作。掌握这套组合不仅能厘清Spring容器与SpringMVC子容器的关系还能深入理解动态SQL、事务管理等关键实践。在实际应用中SSM常被用于构建学生信息管理系统等典型业务场景涵盖登录认证、权限拦截、联表查询与分页展示。从数据库设计到Mapper层实现完整梳理这套系统有助于开发者摆脱纯CRUD的局限进而更轻松地掌握Spring Boot的自动配置思想。吃透这个经典案例Java Web后端原理便不再神秘。 老实说看到这个标题我脑子里冒出来的第一句话是SSM三件套的学生信息管理系统Java后端圈子里流传最广的“毕业设计级别”项目。但你别因为它听着“老”就小看它Spring SpringMVC MyBatis这套组合到今天依然是理解Java Web后端原理的最佳入口。很多同学手里有这份源码却不知道从哪看起更多人想自己完整写一套又卡在配置文件互相协作和框架整合的细节上。这篇笔记就把这套系统从数据库设计到Controller层完整拆一遍把每个关键的“为什么”都讲清楚适合正在学SSM整合的人、准备Java实习面试的人以及想把手头这份源码真正吃透而不是只跑通的人。1. 这个SSM学生信息管理系统到底在解决什么问题1.1 为什么这类项目永远是Java面试的高频题先说一个很现实的现象你去翻Java后端岗位的面试题“SpringMVC工作流程”“MyBatis中#和$的区别”“Spring三级缓存原理”常年霸榜。面试官为什么爱问这些因为SSM这套东西恰好覆盖了一个Java Web项目从请求进入、参数绑定、业务处理、数据库访问到响应返回的完整链路。学生信息管理系统就是这条链路的完美载体。它不是一个只做增删改查的工具而是一个包含登录认证、权限拦截、联表查询、分页展示、数据统计的真实业务系统。面试官问“你有没有做过完整项目”如果你只答“用Spring Boot MyBatis-Plus生成器敲了套CRUD”那等于没做但如果你能说清楚“我用Spring管理Service层的Bean用SpringMVC处理请求转发和参数校验用MyBatis手写了动态SQL做条件分页查询”这就完全不一样了。所以这套源码的真正价值不是那几百个Java文件而是它提供了一个“用最小成本理解大型Java Web项目运行机制”的样本。你把这份源码的每一层都弄懂了再去看Spring Boot会感觉像看简化版的洋娃娃一眼就能看穿它的自动配置在干什么。1.2 选型逻辑这个场景为什么用SSM而不是Spring Boot可能有人会问现在新项目谁还用Spring SpringMVC MyBatis手写配置Spring Boot不香吗这话没错但注意这套系统的定位是教学和原理学习。Spring Boot的本质是“约定优于配置”它把SSM整合时那些繁琐的XML配置全部自动化了。好处是开发效率高坏处是你根本不知道底层发生了什么。我见过太多用Spring Boot写得很溜的人问他“DispatcherServlet在哪配置的”他一脸茫然。而在SSM项目里web.xml中的DispatcherServlet声明、ContextLoaderListener的加载顺序、applicationContext.xml和springmvc.xml两个容器的关系全都摆在明面上。你亲手配一遍才会真正理解SpringMVC的工作原理之后学Spring Boot才会事半功倍。另外这个学生信息管理系统如果用Spring Boot写可能几十行代码就完事了但也就失去了“拆解”的价值。SSM版本的代码量虽然大但每一段配置都有它存在的理由每一个包结构都有清晰的职责边界。这就是为什么我在带人的时候宁可让大家先从SSM手写一遍而不是直接上Spring Boot。工具可以迭代但底层原理不会过时。2. 系统功能与数据库设计拆解2.1 功能模块划分麻雀虽小五脏俱全这套学生信息管理系统虽然叫“学生信息管理”但它的功能边界比字面意思要宽不少。拿到源码之后我建议你先不要急着看代码而是先梳理功能模块。通常一个完整的学生信息管理系统至少包含以下模块用户登录与权限控制管理员、教师、学生三类角色登录后根据角色跳转到不同页面未登录用户不能访问内部页面。学生信息管理学生基本信息的增删改查支持按姓名、班级、学号等条件模糊查询和分页展示。班级管理维护班级信息和年级信息学生表通过班级ID关联班级表。课程管理课程的增删改查一门课程会绑定一个授课教师包含课程名称、学分等字段。成绩管理录入学生各科成绩支持按学生或课程维度查询能计算总分、平均分等统计信息。个人信息维护普通用户登录后可以查看或修改自己的基础信息比如联系方式、密码等。每个模块看起来简单但它们之间的数据关系是真实的业务关系。比如你要删一个班级那班级下的学生怎么处理是禁止删除还是级联删除这些逻辑都是面试官喜欢追问的点也是这套源码值得仔细品的地方。从源码的Controller层你就能看出作者对模块边界的设计思路哪些功能收敛在一个Service里、哪些功能跨Service协作这些都是可以复盘的内容。2.2 数据库表设计五张核心表怎么关联数据库设计是整个系统最容易被新手忽略、但实际最重要的部分。学生信息管理系统虽然业务量不大但表结构设计是标准的“学生-课程”多对多模型变体。核心表我拆成下面这些表名核心字段说明t_useruser_id, username, password, role登录用户表角色区分管理员/教师/学生t_studentstudent_id, student_no, name, gender, birthday, phone, email, clazz_id学生信息表通过clazz_id关联班级t_clazzclazz_id, clazz_name, grade班级表存班级名称和所属年级t_coursecourse_id, course_name, credit课程表学分用于成绩统计t_scorescore_id, student_id, course_id, score, exam_date成绩表学生和课程的多对多关联表这张表设计最核心的就是t_score这个中间表。学生表和学生表之间不直接关联而是通过t_score表形成“学生-课程”的多对多关系。每一条成绩记录都指向一个学生和一门课程这样就能实现“查某个学生的所有成绩”“查某门课的所有学生”这种典型需求。在SQL层面典型联查就是三表关联t_score join t_student join t_course。如果你在源码里看到按学生姓名查成绩的SQL本质就是用student_name字段去过滤t_student表再关联出t_course的课程名称。这个模型吃透了以后遇到订单-商品-用户、文章-标签-分类这种多对多场景你都会秒懂。字段的类型设计也有讲究。日期字段在MyBatis中建议用java.util.Date映射数据库侧用datetime而不是varchar否则排序和范围查询都会被字符串比较坑到。性别这种字段用char(1)或者tinyint都行但tinyint更省空间也更符合日常开发习惯。学号这种业务唯一字段建议建唯一索引并且用varchar而不是int因为学号可能有前导零存成int就丢了。2.3 项目分层结构Controller-Service-Mapper三层怎么拆打开源码的包结构你会发现它严格遵循经典的MVC三层设计。我见过的SSM项目结构可以总结成一个黄金模板这套源码基本上也是按这个模板来的com.example.student ├── controller // 接收请求参数校验调用Service ├── service // 业务逻辑层处理事务边界 │ └── impl // Service接口实现类 ├── mapper // MyBatis Mapper接口层 ├── entity // 实体类对应数据库表 ├── interceptor // 拦截器如登录拦截 ├── common // 公共类响应结果封装、分页对象等 └── config // 可选Java配置类Controller层只做三件事接收前端参数、调用Service、把结果封装后交给视图渲染。绝对不要在这一层写业务逻辑比如“查成绩之前先判断角色权限”这种代码放在Service层里才是对的。Service层是业务逻辑的集中地事务边界也在这里划。比如录入成绩时要同时更新学生平均分这两步必须在一个事务里那就把这两个操作放到同一个Service方法里加上Transactional注解。这个注解是Spring管理的核心价值之一用的好不好直接反映你对事务的理解。Mapper层就纯粹是数据库访问接口一个接口方法对应一条SQL。MyBatis的Mapper接口之所以能直接注入到Service里靠的是mybatis-spring包中的MapperScannerConfigurer做动态代理。你不需要写实现类MyBatis会为接口自动生成代理对象这也是SSM整合让人最爽的地方之一。3. 核心配置与代码实现详解3.1 环境准备与技术版本选择如果你打算本地把这套源码跑起来我建议先统一环境版本能省掉一半的报错时间。这套项目要求不高JDK 1.8以上、Maven 3.6以上、Tomcat 8.5或9.0、MySQL 5.7或8.0都可以。有一个细节要重点提醒MySQL 8.x和5.x的JDBC驱动类名不一样MySQL 5.x用com.mysql.jdbc.DriverMySQL 8.x用com.mysql.cj.jdbc.Driver。MySQL 8.x还必须加上时区参数serverTimezoneAsia/Shanghai否则连接池初始化就会报时区错误。这是新手最常见的第一个拦路虎。数据库脚本一般会在源码里以student.sql这类文件给出你只需要在MySQL里source一下或者用Navicat运行SQL文件。导入之后注意检查一下数据库名是否和jdbc.properties里的配置一致不一致的话改配置文件即可。创建Maven项目时依赖坐标建议统一使用Spring 5.x版本不要用Spring 4.x因为5.x对Java 8支持更好而且不需要额外引入javax.annotation依赖。3.2 web.xml的双容器机制在SSM整合中web.xml是整个应用的启动入口。这一步值得花最多时间理解因为它决定了Spring和SpringMVC两个容器是怎么配合的。web-app !-- 1. Spring根容器配置 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- 2. SpringMVC前端控制器 -- servlet servlet-namedispatcherServlet/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:springmvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcherServlet/servlet-name url-pattern//url-pattern /servlet-mapping !-- 3. 字符编码过滤器 -- 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 /web-app这里面的核心机制是双容器ContextLoaderListener加载applicationContext.xml创建Spring根容器负责管理Service、Mapper、数据源等业务组件DispatcherServlet加载springmvc.xml创建SpringMVC子容器负责管理Controller、视图解析器、拦截器等Web组件。SpringMVC子容器可以访问父容器中的Bean但父容器不能访问子容器的Bean。所以Controller可以注入Service但Service不应该注入Controller。这也是为什么在applicationContext.xml中扫描包时要过滤掉Controller而在springmvc.xml中要只扫描Controller的原因。如果你不加过滤两个容器重复扫描同一个包会看到service被创建两次甚至出现AOP代理失效的诡异问题。3.3 Spring根容器配置applicationContext.xml这个配置文件管的是整个项目的“地基”。数据源、事务、Mapper扫描、Service层组件全在这里声明。核心配置片段长这样context:component-scan base-packagecom.example.student context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan context:property-placeholder locationclasspath:jdbc.properties/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /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.example.student.mapper/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/component-scan里的排除配置是精髓。Spring容器只管理Service、Mapper等组件不管理Controller。然后通过batis的SqlSessionFactoryBean把数据源和Mapper XML关联起来注意mapperLocations配置的值通常是classpath:mapper/*.xml意思是扫描resources目录下mapper文件夹里的所有XML映射文件。数据源我建议直接使用Druid。它有监控页面、连接池管理、防SQL注入等功能。如果你看到的源码里用的是dbcp或c3p0也可以直接换掉性能差别不大但Druid的监控功能排错时很好用。事务管理器是DataSourceTransactionManager配合Transactional注解使用注意tx:annotation-driven这一行一定要配否则Service里的Transactional不会生效。3.4 SpringMVC容器配置springmvc.xmlSpringMVC的配置相对简洁但同样有两个坑要注意。第一个是默认的“扫描所有注解”会把Controller以外的组件也扫进来导致容器职责混乱第二个是静态资源会被DispatcherServlet拦截需要单独放行。context:component-scan base-packagecom.example.student use-default-filtersfalse context:include-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan mvc:annotation-driven/ mvc:default-servlet-handler/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/logout/ mvc:exclude-mapping path/static/**/ bean classcom.example.student.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptorsmvc:annotation-driven启用了SpringMVC的注解驱动包括参数解析、JSON转换、类型转换等一堆默认功能。如果你忘了写这一行RequestBody、ResponseBody都会失效。default-servlet-handler是放行静态资源的否则你的CSS、JS、图片全会被DispatcherServlet拦走样式全崩。视图解析器配置很简单prefix Controller返回的字符串 suffix就构成了最终的JSP路径。比如Controller返回student/list实际渲染的是/WEB-INF/views/student/list.jsp。放在WEB-INF下有个好处用户不能直接通过URL访问JSP页面必须经过Controller在安全性上是一个天然屏障。拦截器配置是重点。这个LoginInterceptor的作用是没有登录的用户访问任何内部页面统一重定向到登录页。mvc:exclude-mapping指定了哪些路径不拦截这里放行了登录页、退出登录和静态资源。如果你在源码里看到拦截器没生效先检查是不是忘了写mvc:mapping path/**。3.5 MyBatis映射文件与动态SQLMyBatis是这套系统的数据访问层也是最值得手写代码的地方。在学生信息管理系统中最典型的需求就是条件查询加分页这时候动态SQL就派上用场了。StudentMapper.xml里通常会有这么一段select idselectByCondition resultTypecom.example.student.entity.Student SELECT * FROM t_student where if teststudentName ! null and studentName ! AND name LIKE CONCAT(%, #{studentName}, %) /if if testclazzId ! null AND clazz_id #{clazzId} /if /where ORDER BY student_id LIMIT #{offset}, #{pageSize} /selectwhere标签会智能去掉第一个多余的AND这是MyBatis的经典技巧。if标签让我们可以根据条件动态拼接SQL实现“姓名模糊查询 班级精确过滤”的联合查询。这一段的面试价值极高一定要能默写出来。LIMIT #{offset}, #{pageSize}是分页的核心offset (pageNum - 1) * pageSizepageNum从1开始。关于MyBatis的#和$的区别所有面试官都会问这里必须展开讲。使用#{name}时MyBatis会把参数预编译成?占位符然后通过PreparedStatement设置参数这样用户输入再特殊也不会拼接进SQL能从根上防SQL注入。而使用${name}是简单的字符串替换直接把参数值拼接到SQL语句中如果参数是用户输入的就有被SQL注入的风险。所以在DML语句的where条件、values值这些位置永远用#。那$有没有用当然有当你要动态指定表名、列名、排序字段时因为表名、列名不能预编译占位只能字符串拼接这时候才用$而且必须做白名单校验。一句话总结#安全但只能传值$灵活但只能用在结构位置。4. 核心业务场景落地与经验4.1 登录认证与SpringMVC拦截器实践登录功能看起来简单但是整套系统安全性的第一道关卡。Controller层接收用户名密码调用Service层的userService.login(username, password)方法查询数据库比对密码。比对通过之后把用户对象放进session然后在后续的请求中通过拦截器检查session里有没有这个用户。LoginInterceptor是最典型的SpringMVC拦截器实现核心代码如下public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object loginUser session.getAttribute(loginUser); if (loginUser ! null) { return true; } // 未登录重定向到登录页 String contextPath request.getContextPath(); response.sendRedirect(contextPath /login); return false; } Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // 视图渲染前的处理比如给所有页面注入公共数据 } }这个preHandle方法在Controller方法执行之前调用如果返回false请求直接中断不会进入Controller。利用这个机制我们实现了“未登录不能访问任何内部页面”的强制约束。这个地方要注意一个细节重定向时要加上request.getContextPath()前缀否则如果你的应用部署在非根路径下跳转地址会404。我第一次写的时候就没加这个前缀本地Tomcat根路径一切正常部署到服务器二级路径后登录失效排查了很久才发现是这个原因。用户密码的存储在这个学生管理系统里一般用的是MD5或SHA-256加密。如果你在源码里看到的是明文存储建议改成加盐后哈希。虽然学生管理系统不是高安全场景但养成不存明文密码的习惯对以后进企业做真实项目非常重要。可以引入Shiro或Spring Security来管权限但先用拦截器把登录状态管好理解原理更有价值。4.2 条件分页查询学生列表页的完整链条学生列表是这个系统使用频率最高的页面。前端传页码、每页条数、查询条件后端返回当前页数据和总记录数。这一条链路值得你完整刷一遍因为它是所有管理系统的通用范式。Service层通常会这样写public PageResultStudent findPage(StudentQuery query) { int offset (query.getPageNum() - 1) * query.getPageSize(); query.setOffset(offset); ListStudent list studentMapper.selectByCondition(query); long total studentMapper.countByCondition(query); return new PageResult(list, total); }这里有两个SQL一个查列表一个查总数二者组合才能实现分页。注意count查询和列表查询的查询条件必须保持一致否则总数对不上。为了避免两套where条件重复写可以把查询条件封装成一个Query对象分别传给两个语句的parameterType。如果你没手写LIMIT而是用PageHelper插件原理也一样拦截器会自动把查询SQL包装成带COUNT的SQL再生成LIMIT语句。手写SQL的好处是能看清分页本质理解PageHelper到底做了什么。用PageHelper时有一点要留心它基于ThreadLocal实现query调用的第一条SQL会被分页拦截如果前面还有别的SQL也会被误分页。所以分页调用要紧接着Mapper查询中间不要插入其他数据库操作。关于分页时前端展示“共X条当前第X页”这种交互Controller里把PageResult对象放到Model里JSP通过JSTL标签或者Vue来渲染都可以。老SSM项目里用JSTL较多格式大概是forEach遍历items${page.list}再通过${page.total}取总数。源码的这一段对刚接触MVC的人非常友好能看明白后端数据是怎么一步步变成前端页面的。4.3 成绩统计中的SQL实践联查与聚合函数成绩管理模块是这套系统里SQL含量最高的地方。典型需求包括查询某个学生的全部成绩、查询某门课程所有学生的成绩、计算某门课程的平均分和最高分。这些操作都需要多表关联和聚合函数。查询一个学生的成绩列表SQL可以这样写SELECT s.student_no, s.name AS student_name, c.course_name, sc.score, sc.exam_date FROM t_score sc LEFT JOIN t_student s ON sc.student_id s.student_id LEFT JOIN t_course c ON sc.course_id c.course_id WHERE s.student_id #{studentId} ORDER BY sc.exam_date DESC注意这里用了LEFT JOIN而不是INNER JOIN目的是保证即使某条成绩记录的学生信息缺失也能保留成绩记录本身便于排查数据问题。实际开发中联查优先用LEFT JOIN除非你明确知道关联记录必然存在。计算课程平均分就是用AVG函数配合GROUP BYSELECT c.course_name, COUNT(sc.score_id) AS student_count, AVG(sc.score) AS avg_score FROM t_score sc LEFT JOIN t_course c ON sc.course_id c.course_id GROUP BY c.course_id这个统计结果在系统中通常以图表形式展示比如柱状图对比不同课程的平均分。如果你在源码里看到返回给前端的是一个ListMapString, Object那也是很常见的做法因为统计结果不一定能对应到某个实体类用Map更灵活。此时在Mapper.xml里resultType直接写map即可MyBatis会自动把列名转为Map的key。这一招在写报表类需求时很实用。5. 我踩过的坑常见问题排查实录5.1 新手最容易翻车的异常对照速查表我把这几年带人跑SSM项目时遇到的高频问题整理成一张表每一项都是真实案例。这张表建议收藏遇到问题时先对照一遍能省下大量排查时间。现象根本原因处理办法Tomcat启动报ClassNotFoundException: ContextLoaderListener缺少spring-web、spring-webmvc依赖或依赖没有打到WEB-INF/lib检查pom.xml确认Spring相关依赖scope不为provided执行mvn clean package后看war包内是否有jar启动时NoSuchBeanDefinitionException: sqlSessionFactorySqlSessionFactoryBean没有被实例化检查applicationContext.xml是否被ContextLoaderListener正确加载classpath路径是否正确Invalid bound statement (not found)Mapper接口和方法在XML中没有对应Mapper接口名与XML的namespace完全一致XML文件名和Mapper接口名一致且放在同包路径下Servlet.service()抛NullPointerExceptionService中的Mapper为nullMapper接口没有被扫描到检查MapperScannerConfigurer的basePackage是否指向了Mapper接口所在包访问页面时JS/CSS全部404DispatcherServlet把静态资源拦截了配置mvc:default-servlet-handler或用resource映射静态资源路径中文乱码往数据库写入后和前端显示不一致缺少字符编码过滤器或数据库连接URL没有指定UTF-8配置CharacterEncodingFilterjdbc.url中追加useUnicodetruecharacterEncodingUTF-8密码框登录成功但跳转后又是登录页session中没有保存登录用户或拦截器路径配置错误检查登录成功后是否session.setAttribute(loginUser, user)检查拦截器exclude-mapping是否放行了登录页MySQL报Public Key Retrieval is not allowedMySQL 8加密插件导致连接串缺少参数jdbc.url加allowPublicKeyRetrievaltrueuseSSLfalse项目部署到服务器后404但本地正常context-path导致重定向或资源路径写死所有跳转用request.getContextPath()拼接或前端用相对路径这张表里最容易埋人的是Invalid bound statement这个错。它的出现通常是你把StudentMapper.xml放在了src/main/java的接口包下而Maven打包时默认不编译XML文件导致target目录里根本没有这个XML。正确的做法是把XML放在src/main/resources/mapper目录下或者在pom.xml里显式配置资源目录。我在公司带实习生时这个坑十个人里至少有六个人踩过。5.2 数据库连接与事务的隐性坑事务问题是最难排查的一种因为代码看起来完全没问题但数据就是不回滚。最常见的症状是Service方法有Transactional方法里第一步insert成功第二步update抛异常回滚后数据库里insert的数据还在。这种情况十有八九是事务没有生效。可能的根源包括数据源没有配置transactionManager或者tx:annotation-driven没有配置导致注解被无视还有一种情况是ServiceImpl类本身没有被Spring扫描而是被New出来那自然没有代理还有一种是方法内部通过this调用另一个方法事务不会触发生效因为代理对象只拦截外部调用。解决办法是外部注入自身的代理对象或者把需要事务的方法放到另一个Service类里。另外Druid连接池的maxActive、initialSize这些参数建议按实际并发量调整。学生管理系统并发量很小初始5、最大20足够。如果你是本地学习把maxWait设置成60000毫秒避免连接池饿死的时候报错报得太突然。注意在排查数据库问题时先看一下Druid的监控页面能省很多事默认地址是项目路径/druid/index.html。5.3 部署到Tomcat的操作细节最后说说怎么把项目部署到Tomcat。开发时可以直接在IDEA里配置Tomcat Server用war exploded模式热部署。但如果你要把源码交付给别人或者放到服务器上跑建议打war包部署。在pom.xml中确认packaging是war然后在IDEA中执行mvn clean package。构建完成后把target目录下的student.war复制到Tomcat的webapps目录下启动Tomcat后它会自动解压。启动日志显示Deployment of web application archive has finished后就能访问了。如果服务器上用的是Nginx需要把静态资源的请求直接代理到Tomcat动态请求也要加上正确的context-path。有一个非常容易被忽略的问题MySQL驱动版本和MySQL服务端版本不匹配。比如MySQL 8.0的服务器用5.1.x的驱动虽然有时候能连上但一遇到新加密插件就会报错。我的建议是直接用mysql-connector-java 8.0版本官方支持向后兼容MySQL 5.7和8.0。同时openjdk版本和Tomcat版本也要匹配Tomcat 9搭配JDK 8或11都很稳妥。我在实际部署中还遇到过一个问题本地Windows下跑得好好的放到Linux服务器上就报时区错误。根本原因是MySQL连接的serverTimezone参数没有指定Windows和Linux的默认时区不一样。解决方案就是前面说的在JDBC URL上显式指定serverTimezoneAsia/Shanghai。结尾一点个人的体会如果你把这份源码从头到尾自己照着敲一遍而不是光看你会发现SSM这套东西其实不难但它逼着你把每一层之间的契约都搞清楚Spring管理对象、SpringMVC接请求、MyBatis管SQL三个框架各自有清晰的分工老老实实通过配置文件和接口协议协作不藏着掖着。这也是我为什么始终觉得SSM是进阶Spring Boot之前绕不开的一课。你可以从这个项目开始先把它跑通然后把数据库表扩展一两张、加一个新模块比如“选课”“宿舍管理”改动过程中遇到什么问题就去查配置和源码。这样一个项目做完你对Java Web后端开发的感觉会完全不一样。本文还有配套的精品资源点击获取
返回列表