简介:这是一份基于SSM框架与MySQL数据库的房屋租赁系统毕业设计资料包,面向Java方向学生、毕业设计开发者及需要快速搭建在线租赁平台的初学者。资源以单个docx文档形式呈现,压缩包大小1.48MB,内含毕业设计论文全文,覆盖需求分析、系统设计、数据库设计、系统实现与测试总结等完整环节,并附有关键词与英文摘要,便于对照参考。文档围绕房产新闻发布、房东与租户在线注册、在线预约和交流等功能模块展开,结合SSM框架与JSP技术,详细阐述从租房行业现状分析到系统具体实现的完整过程,可帮助读者理解SSM+MySQL项目结构的搭建思路与编码逻辑。同时,文档还包含国内外研究现状、论文组织结构及相关技术介绍,能帮助读者快速把握毕业设计写作规范与系统设计脉络。目前已有90人学习下载,对于正在撰写Java方向毕业设计论文或准备房屋租赁系统项目的学生来说,是一份兼具参考价值和实践指导意义的资料。
1. 这套源码到底解决谁的什么问题
如果你正在为毕业设计或者课程设计四处找项目,大概率见过「基于SSM+MySQL的房屋租赁系统」这种标题。它听起来像是一个普通的CRUD练习,但实际上是一个麻雀虽小、五脏俱全的经典Web应用:前端页面负责展示房源和提交订单,后端用Spring管理对象、SpringMVC接收请求、MyBatis操作数据库,MySQL存房源、租客、合同、订单这些核心业务数据,最后再配一份写好的论文文档,从需求分析到数据库设计再到系统测试全都覆盖。很多同学第一反应是「这会不会太简单了」,但真正上手才发现,SSM三个框架的整合细节、表与表之间的外键关系、分页查询和登录拦截这些点,恰恰是面试和答辩时最容易被抓着追问的地方。
这套东西适合谁?一类是JavaWeb刚入门、想用一套完整项目把SSM串起来的初学者,另一类是时间紧、需要快速搭建一个可演示可答辩系统的应届生。它解决的核心问题不是「租房业务有多复杂」,而是「怎么用最主流的技术栈把一套带数据库、带论文、能跑通的系统在短时间内落地」。接下来的内容我会按真实开发顺序拆开讲:框架为什么这么选、数据库怎么设计、代码分层怎么写、哪些坑我替你踩过,以及最后那份docx论文该怎么改才能过查重和答辩。
2. SSM+MySQL的组合为什么经久不衰:框架分工与选型逻辑
2.1 三个框架各管哪一段:从请求到数据库的完整链路
先说清楚SSM到底在干什么。一个典型的房屋租赁请求,比如租客在页面上点「提交订单」,数据流向是这样的:浏览器发出HTTP请求,SpringMVC的DispatcherServlet最先接到,它根据URL找到对应的Controller方法;Controller不直接写SQL,而是调用Service层;Service层处理业务逻辑(比如检查房源是否已被租、计算租金总额),然后通过MyBatis的Mapper接口向MySQL发SQL;查询结果再一层层返回,最终由SpringMVC渲染成JSON或JSP页面回给浏览器。而Spring在整个过程中扮演黏合剂角色——所有Controller、Service、Mapper对象的创建和注入都由Spring容器管理,事务也由Spring统一控制。
选型理由得从维护性和学习成本两个角度看。相比SpringBoot的自动配置,SSM每一步都要手动声明Bean,确实繁琐,但它把「对象怎么创建、依赖怎么注入、事务怎么生效」这些底层机制暴露得很清楚。对于需要写论文的人来说这是优势:论文里「系统架构设计」一章有大量内容可写,Spring的IoC容器、SpringMVC的前端控制器模式、MyBatis的ORM映射,每一个都能展开成小节。而且这套技能在老旧系统维护场景中依然有真实需求,学会了不吃亏。
2.2 环境版本搭配与最简工程骨架
版本搭配是第一个坑。网上很多教程还在用MySQL 5.7搭配老驱动,但新装的MySQL 8.0+在连接URL、驱动类名、时区设置上都有变化。我个人建议的组合是:JDK 1.8、Tomcat 8.5或9.0、Maven 3.6+、MySQL 8.0、Spring 5.x、MyBatis 3.5.x。如果你拿到的源码是MySQL 5.x时代的产物,需要改三处:驱动类从com.mysql.jdbc.Driver改成com.mysql.cj.jdbc.Driver,URL里加上serverTimezone=Asia/Shanghai,同时确认useSSL=false,否则启动时容易报时区或SSL警告。
工程骨架用Maven的标准结构就够了,不需要额外插件。以下是最小化的pom.xml核心依赖,我加了注释说明每个依赖的用途:
<dependencies> <!-- Spring核心容器,管理所有Bean的生命周期与依赖注入 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.20</version> </dependency> <!-- Spring对JDBC和事务的支持,事务管理器依赖它 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.3.20</version> </dependency> <!-- SpringMVC,负责Web层的请求分发与参数绑定 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>5.3.20</version> </dependency> <!-- MyBatis核心,以及Spring整合MyBatis的桥接包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.10</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <!-- MySQL 8.x驱动,注意group id里是mysql不是mysql-connector-java旧坐标 --> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency> <!-- 数据库连接池,推荐druid,自带监控和防SQL注入 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency> <!-- JSON序列化,Controller返回@ResponseBody时用 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.13.4</version> </dependency> <!-- JSP的JSTL标签库,页面层渲染用 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies>依赖版本是血泪经验的浓缩。Spring 5.3.x搭配MyBatis 3.5.x这条线很稳,但如果你非要用Spring 6或SpringBoot 3.x,那整套代码就得升级到Jakarta命名空间,源码里的javax.servlet全部要改成jakarta.servlet,改动量瞬间变大。所以拿到一套SSM源码,先别急着跑,第一件事就是核对JDK、Tomcat、MySQL的版本与你本机环境是否兼容,版本不匹配是绝大多数「跑不起来」的根源。
2.3 配置文件三件套:spring-mvc.xml、applicationContext.xml、jdbc.properties
SSM的配置是出了名的繁琐,但核心就三份文件。jdbc.properties存数据库连接信息,applicationContext.xml管Spring容器(包括数据源、事务、Mapper扫描),spring-mvc.xml只管Controller层的组件扫描和视图解析器。我见过太多人把配置全塞进一个文件里,结果事务不生效、Mapper找不到,排查半天。分开写,职责清晰,出问题也容易定位。
先看jdbc.properties:
# MySQL 8.x连接配置,serverTimezone必须加,否则报时区错误 jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/house_rent?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456 # druid连接池参数,initialSize建议不低于5,maxActive控制在20以内 jdbc.initialSize=5 jdbc.maxActive=20 jdbc.minIdle=5 jdbc.maxWait=60000然后是spring-mvc.xml和applicationContext.xml的分工。applicationContext.xml里要配三样东西:数据源、SqlSessionFactoryBean(指定实体类别名和Mapper XML的位置)、MapperScannerConfigurer(扫描Mapper接口所在包)。spring-mvc.xml里则只做两件事:开启<mvc:annotation-driven/>和扫描controller包。我自己踩过的坑是:如果在spring-mvc.xml里把service包也扫了,事务注解会被SpringMVC的容器先扫描到,导致事务管理失效——因为Spring容器和SpringMVC容器是父子关系,子容器扫了父容器本该管的Bean,代理就乱了。
2.4 项目骨架搭建:从空工程到能启动的最短路径
配好依赖和三个核心配置文件后,要验证工程真的能启动。最短路径是:先建一个测试Controller返回字符串,配置好Tomcat,跑通一次完整的请求-响应链路,再去填业务代码。别一上来就写房屋增删改查,那样出了问题根本分不清是配置错还是代码错。
以下是用Maven创建Web工程并验证最小链路的一组命令(Windows环境):
# 1. 用Maven骨架创建webapp工程,groupid和artifactid按自己项目改 mvn archetype:generate -DgroupId=com.example -DartifactId=house-rent -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false # 2. 进入工程目录,先只做编译,确认依赖全部下载成功 cd house-rent mvn clean compile # 3. 启动Tomcat插件(需要在pom里配置tomcat7-maven-plugin或使用本地Tomcat) mvn tomcat7:run注意第三步的tomcat7:run在JDK 1.8环境通常没问题,但如果你用的是Tomcat 9,建议直接打包成war丢进本地Tomcat的webapps目录启动:mvn clean package后把生成的war复制过去,比插件更接近真实部署环境。验证最小链路时,Controller写一个返回字符串的方法就够了:
@Controller public class PingController { @RequestMapping("/ping") @ResponseBody public String ping() { // 返回值直接写入响应体,不走视图解析器 return "pong"; } }这个@ResponseBody注解是关键,它告诉SpringMVC不要找JSP页面,直接把字符串写回浏览器。如果访问http://localhost:8080/house-rent/ping能看到pong,说明Spring容器、SpringMVC、Tomcat三者已经连通,接下来再往里面填业务代码就有底气了。参数层面,如果返回中文出现乱码,检查spring-mvc.xml里是否配置了StringHttpMessageConverter的UTF-8编码——这是初学者最容易忽略的一步。
3. 房屋租赁系统的数据库设计:五张核心表与SQL脚本
3.1 从租房业务反推表结构:租客、房源、订单如何关联
数据库设计是论文里「系统设计」章节的重头戏,也是面试时最容易被追问的部分。房屋租赁系统的核心业务链路是:房东发布房源 → 租客浏览房源 → 租客提交订单 → 房东接单 → 生成合同。围绕这条链路,最少需要五张表:用户表(区分房东和租客)、房源表、订单表、合同表、收藏表。这五张表把核心业务闭环了,扩展的话再加一个公告表或反馈表。
表与表之间的外键关系要能讲清楚,这是答辩时的高频问题:房源表的landlord_id指向用户表的id,代表「这套房是谁发布的」;订单表的house_id、tenant_id分别指向房源表和用户表,代表「谁租了哪套房」;合同表的order_id指向订单表,表示「这个订单最终签了合同」。我最常被问到的一个点:为什么订单表和合同表要拆开?因为不是每个订单都会签约,租客可能下单后又取消,拆开才能忠实记录业务状态。
3.2 建库建表SQL与字段设计中的三个细节
以下是我认为比较干净的一套建表脚本,字段注释都写在SQL里。你拿到源码后第一件事应该是对照这份脚本检查自己的库,很多bug就是字段名和实体类属性对不上导致的。
-- 创建数据库,字符集用utf8mb4而不是utf8,因为utf8存不了emoji和一些生僻字 CREATE DATABASE IF NOT EXISTS house_rent DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE house_rent; -- 用户表:一张表同时承载房东和租客两种角色 CREATE TABLE `user` ( `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID', `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '登录用户名', `password` VARCHAR(100) NOT NULL COMMENT '密码(建议存MD5或BCrypt摘要)', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `role` TINYINT NOT NULL DEFAULT 0 COMMENT '角色:0-租客,1-房东,2-管理员', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; -- 房源表:核心业务表,状态字段要建索引 CREATE TABLE `house` ( `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '房源ID', `landlord_id` INT NOT NULL COMMENT '房东用户ID,关联user.id', `title` VARCHAR(100) NOT NULL COMMENT '房源标题', `address` VARCHAR(200) NOT NULL COMMENT '详细地址', `price` DECIMAL(10,2) NOT NULL COMMENT '月租金(元)', `area` DECIMAL(8,2) DEFAULT NULL COMMENT '面积(平方米)', `house_type` VARCHAR(20) DEFAULT NULL COMMENT '户型,如两室一厅', `status` TINYINT DEFAULT 0 COMMENT '状态:0-待审核,1-已上架,2-已出租', `description` TEXT COMMENT '房源描述', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发布时间', KEY `idx_landlord_id` (`landlord_id`), KEY `idx_status` (`status`), CONSTRAINT `fk_house_landlord` FOREIGN KEY (`landlord_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源信息表';字段设计有三个细节值得专门说明。第一,status字段必须建索引,因为系统首页的房源列表、后台的管理员审核列表都按状态过滤,没有索引数据量一上去就慢。第二,价格字段用DECIMAL(10,2)而不是DOUBLE,租金涉及金额结算,DOUBLE的浮点误差在累计计算时会被放大,DECIMAL是精确数值类型。第三,所有外键字段在对应的业务表里都要有索引——MySQL不会自动为外键建索引,而订单表按tenant_id查历史订单是高频操作,这块漏了我曾经吃过慢查询的亏。订单表、合同表、收藏表的建表逻辑类似,外键分别指向对应的主表即可,不在此全量贴出。
3.3 自增主键还是雪花ID:面试官爱问的取舍
这套项目规模用自增主键完全够用,但答辩时经常会被问到「为什么不用雪花ID这类分布式ID方案」。这个问题要提前准备好回答框架。自增主键的优势是简单、有序、索引性能好;缺点是分表分库时全局唯一性无法保证,而且ID可被遍历(比如订单号是自增的,竞对能通过构造ID抓取业务量)。雪花ID的优势是全局唯一、趋势递增,但需要引入额外的发号器组件,本地单机项目没有这个必要。我建议这样回答面试官:本系统是单体应用、数据量预估百万以内,自增主键配合InnoDB的聚簇索引能获得最好的写入性能,同时业务上不要求ID不可预测,所以选自增。然后把「如果未来要分表,我会迁移到雪花ID并预留好ID类型的扩展空间」这句话也说出来,显得你思考过边界。
4. 从DAO到Controller:SSM分层实现房屋租赁核心功能
4.1 MyBatis Mapper接口与XML:以房源条件查询为例
SSM项目的代码组织通常分四层:entity(实体类)、mapper(MyBatis的Mapper接口)、service(业务接口+实现类)、controller(请求分发)。实体类的属性名要和数据库字段一一对应,但有个常见例外:如果数据库字段用了下划线命名(如landlord_id),实体类属性用驼峰命名(landlordId),那么MyBatis需要开启驼峰映射开关,否则查询结果全是null。这个开关在SqlSessionFactoryBean的configuration属性里配,细节见后面的避坑章节,这里先说明约定。
以「按条件筛选房源」为例,Mapper接口先定义方法:
public interface HouseMapper { /** * 根据组合条件查询房源列表 * @param title 标题关键字,可为null * @param minPrice 最低租金,可为null * @param maxPrice 最高租金,可为null * @param status 房源状态,0-全部 * @return 符合条件且已上架的房源列表 */ List<House> selectHouseList(@Param("title") String title, @Param("minPrice") BigDecimal minPrice, @Param("maxPrice") BigDecimal maxPrice, @Param("status") Integer status); }对应的XML文件里写动态SQL,这是MyBatis最实用的功能:
<select id="selectHouseList" resultType="com.example.entity.House"> SELECT id, landlord_id AS landlordId, title, address, price, area, house_type AS houseType, status, description, create_time AS createTime FROM house <where> <!-- status默认传0时表示查全部已上架房源,这里用!= 0做条件分支 --> <if test="status != null and status != 0"> AND status = #{status} </if> <if test="title != null and title != ''"> AND title LIKE CONCAT('%', #{title}, '%') </if> <if test="minPrice != null"> AND price >= #{minPrice} </if> <if test="maxPrice != null"> AND price <= #{maxPrice} </if> </where> ORDER BY create_time DESC </select><where>标签是我最推荐的写法,它会在至少一个条件成立时自动生成WHERE关键字,同时去掉首个AND,比手动拼WHERE 1=1干净得多。#{...}是预编译参数占位符,MyBatis会把它编译成?再传值,天然防SQL注入,所以严禁用${}拼接用户输入的值——${}是做动态表名或排序字段时才用的。
4.2 Service层事务边界:租房下单为什么要加@Transactional
Service层是SSM项目的灵魂,也是面试重点。以「租客下单租房」为例,这个操作至少涉及两步:往订单表插入一条新订单、把对应房源的状态改成已出租。这两步必须同时成功或同时失败,否则会出现「订单建了但房源还能被别人下单」的数据不一致。所以Service方法上必须加@Transactional注解。
@Service public class OrderServiceImpl implements OrderService { @Autowired private OrderMapper orderMapper; @Autowired private HouseMapper houseMapper; @Override @Transactional(rollbackFor = Exception.class) public boolean createOrder(Long tenantId, Long houseId) { // 业务校验:房源必须存在且当前状态为上架 House house = houseMapper.selectById(houseId); if (house == null || house.getStatus() != 1) { // 房源不存在或已下架,直接抛出运行时异常触发回滚 throw new BusinessException("房源不可预订"); } // 第一步:插入订单,初始状态为待确认 Order order = new Order(); order.setTenantId(tenantId); order.setHouseId(houseId); order.setStatus(0); // 0-待确认,1-已签约,2-已取消 orderMapper.insert(order); // 第二步:锁定房源,状态置为已出租 houseMapper.updateStatus(houseId, 2); return true; } }这个方法的逻辑并不复杂,但有个参数值得单独说:rollbackFor = Exception.class。如果不指定这个值,Spring的默认行为是只在遇到RuntimeException时回滚,遇到IOException这类受检异常时事务照样提交,数据就悬空了。所以我的习惯是永远显式写上rollbackFor,这是血泪经验换来的——以前出过一次文件写入失败但订单照常提交的事故,查了很久才定位到是默认回滚策略的问题。
4.3 Controller参数接收与分页查询集成
Controller层是SSM里最套路化的一层,但参数接收方式有个高频易错点。比如分页查询房源的接口,前端传pageNum和pageSize两个参数,Controller用@RequestParam接收:
@Controller @RequestMapping("/house") public class HouseController { @Autowired private HouseService houseService; /** * 分页查询房源 * @param pageNum 页码,从1开始 * @param pageSize 每页条数,后端强制限制最大50 */ @RequestMapping("/list") @ResponseBody public PageResult listHouse(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) { // 防止有人传pageSize=10000把接口拖垮,这是必须做的兜底 if (pageSize > 50) { pageSize = 50; } return houseService.queryPage(pageNum, pageSize); } }分页的落地方式有两种,取决于你拿到的源码用哪种。常见做法是手动在Mapper里写LIMIT #{offset}, #{pageSize},offset计算方式是(pageNum - 1) * pageSize。另一种是引入PageHelper插件,在applicationContext.xml里配拦截器,Service里直接PageHelper.startPage(pageNum, pageSize),下一页查询自动带limit。我个人偏爱手动写LIMIT,因为SQL对新手更透明,答辩时也更好解释;但如果源码里已经用了PageHelper,别去改它,因为改动会牵连所有查询方法。
4.4 登录拦截器:认证逻辑放哪一层
房屋租赁系统里,房东发布房源、租客提交订单都需要登录后才能操作。SSM里最标准的做法是用SpringMVC的HandlerInterceptor实现登录检查,而不是在每个Controller里重复写判断。实现思路是写一个LoginInterceptor类,实现HandlerInterceptor接口,在preHandle方法里检查Session中是否有登录用户;如果没有,重定向到登录页或返回JSON提示。然后在spring-mvc.xml里用<mvc:interceptors>配置放行规则——登录接口和房源列表页放行,其余路径拦截。
这个设计有三个好处:权限逻辑集中管理、新增接口默认带拦截、不会因为忘记加判断而漏出未授权接口。论文里写「系统安全设计」章节时,这一段是实打实的内容,比空谈安全理论强得多。需要注意的坑是:静态资源(CSS、JS、图片)必须放行,否则页面样式全丢,排查时很容易误以为是前端路径写错了。
5. 部署与运行避坑:环境不一致导致的常见问题排查手册
5.1 数据库连不上:驱动、时区、密码加密三座大山
SSM项目最常见的启动失败就是数据库连接报错。现象是Tomcat启动时报Cannot create JDBC driver of class 'com.mysql.cj.jdbc.Driver'或者Access denied for user。前者说明驱动类名不对——如果你用的MySQL 5.x驱动包,类名是com.mysql.jdbc.Driver,而MySQL 8.x的驱动包里必须用com.mysql.cj.jdbc.Driver,两者不能混用。后者说明jdbc.properties里的账号密码和实际不符合,或者密码里有特殊字符需要转义。
时区问题也是高频坑。连接串里没加serverTimezone=Asia/Shanghai时,MySQL 8.x会报The server time zone value 'Öйú±ê׼ʱ¼ä'这种乱码错误。原因很简单:MySQL驱动拿不到JVM默认时区,而服务器的系统时区又不是UTC。解决方式就是在URL里显式指定时区,别依赖环境默认值。另外一个细节是useSSL=false——本地开发没有配置SSL证书,不关掉会刷大量警告日志,虽然不影响运行但会干扰排错。
5.2 查询结果全是null:驼峰映射与resultMap的坑
这个坑几乎人人都踩过。现象是列表页能查到数据,但每个字段都是null。原因多数是数据库字段名用的是下划线风格(create_time),而实体类属性用的是驼峰风格(createTime),MyBatis默认不会自动把create_time映射到createTime。解决方式两种:全局开启驼峰映射,或是在每个SQL里写AS别名。全局开启一劳永逸,在applicationContext.xml的SqlSessionFactoryBean里加一段配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.example.entity"/> <!-- 关键配置:开启驼峰命名自动映射 --> <property name="configuration"> <bean class="org.apache.ibatis.session.Configuration"> <property name="mapUnderscoreToCamelCase" value="true"/> </bean> </property> </bean>注意mapperLocations的路径classpath:mapper/*.xml,它决定了你的Mapper XML文件放在哪里。常见问题是你把XML放在了src/main/java目录下,但Maven默认只把src/main/resources下的文件打包进classpath,所以XML没被打进war包,启动时MyBatis会报Invalid bound statement (not found)。解决方式要么把XML移到resources/mapper目录下,要么在pom.xml的build节点里加resources配置,把src/main/java下的XML也纳入打包范围。两种方式我更推荐前者——这是Maven工程的约定,改了打包配置反而会增加排查难度。
5.3 事务不生效:三处配置缺一不可
@Transactional不生效的排查路径很固定。现象是Service方法里前一步插入成功,后一步抛出异常,但数据库里前一步的数据还是提交了。原因往往是三处配置没齐:spring-context.xml里没配<tx:annotation-driven transaction-manager="transactionManager"/>、没配DataSourceTransactionManager、或者Service实现类被SpringMVC的子容器扫描到了(如前所述)。另外注意@Transactional只能加在public方法上,如果你加在private方法上,Spring的代理机制根本拦不到这个方法——这也是一个容易忽略的点。
5.4 中文乱码:从Tomcat到数据库的四层编码链
中文乱码是SSM项目的顽疾。现象是页面提交的中文数据存进数据库后显示为乱码,或者查询返回的中文在页面上变成问号。排查要从四层链路依次看:请求层(Tomcat的URIEncoding是否设为UTF-8)、SpringMVC的CharacterEncodingFilter是否配置(且forceEncoding=true)、数据库连接串是否带characterEncoding=utf8、数据库表和字段的字符集是否是utf8mb4。四个环节任何一个断了都会乱码。我最常碰到的场景是前三层都配好了,但建库时忘了指定字符集,表默认用了Latin1,怎么改代码都没用——必须用ALTER TABLE xxx CONVERT TO CHARACTER SET utf8mb4转一遍。
5.5 端口占用与Tomcat反复启动失败
开发时Tomcat启动失败,十次里五六次是端口被占。现象是启动日志一闪而过,报Port 8080 was already in use。解决方式:Windows下用netstat -ano | findstr 8080查占用进程的PID,然后taskkill /F /PID <pid>杀掉;或者在IDE里改Tomcat端口。这个坑本身不难,但因为和代码无关,经常排查半天才发现是上个没关干净的程序还占着端口。我的习惯是启动前先查一次端口,顺手也把CATALINA_HOME配好,避免两个Tomcat实例互相干扰。
6. 论文写作与答辩:docx里的内容层次和三个答辩必答题
拿到源码后,论文docx的写作是最后一道坎。大多数同学的最大误区是照着代码翻译,把类和方法的说明平铺直叙写一遍。论文要的不是「记录代码」,而是「论证你的设计合理」。正确的内容层次应该是这样:需求分析章节写清楚系统面向谁、有哪些角色、每个角色有哪些用例,配用例图最合适;系统设计章节画系统架构图、功能模块图、数据库E-R图,再用文字说明为什么这样分表;核心功能实现章节挑两到三个有代表性的功能(比如登录拦截、房源分页查询、下单事务处理),配合截图和关键代码讲解;测试章节用表格给出测试用例、预期结果和实际结果,再附几个测试截图。
答辩时老师最爱问三个问题。第一个是「为什么选择SSM而不是SpringBoot」,这个问题不要贬低SpringBoot,而是强调SSM更利于理解框架原理、分层更清晰,SpringBoot的自动配置会掩盖很多细节,对于学习阶段来说SSM是更好的训练。第二个是「表设计为什么要拆订单和合同」,答案在前面已经讲过——并非所有订单都会签约,拆分是为了忠实记录业务流程。第三个是「系统的安全性如何保证」,答案至少要有三点:密码不存明文(MD5或BCrypt摘要)、MyBatis的#{}预编译防SQL注入、登录拦截器控制未授权访问。把这三个问题提前写成稿子背熟,答辩的底气会完全不一样。
最后分享一个写论文的习惯:我一般先用表格把所有图表列出来(E-R图、用例图、架构图、流程图、表格),再把每个图表对应的解释文字像填空一样补进去。因为评阅老师翻论文时第一眼扫的是图表,图和表比文字更抓眼球;图表数量够、标注清晰,论文的整体质量分就不会低。这套「图先行、文填坑」的方法我用了很久,希望帮到你。
本文还有配套的精品资源,点击获取