ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SSM公寓房屋出租系统实战:从数据库设计到部署排坑全解析

2026/10/4 12:15:22 拓冰建站 浏览量
SSM公寓房屋出租系统实战:从数据库设计到部署排坑全解析 毕设季又被SSM刷屏了。如果你打开这篇内容大概率是在为“公寓房屋出租系统”这类JavaWeb选题发愁——课设、毕设、甚至公司内部练手项目总逃不过这几个关键词的组合javaweb、mysql、ssm、jsp、easyui、echarts。坦白说这套组合选得很典型甚至可以说有点“过时”但它恰恰是JavaWeb生态里最经典、资料最全、最不容易卡壳的路线。今天我就以这个“ssm公寓房屋出租系统”为例把整个项目的思路、数据库设计、代码实现和那些你十有八九会踩的坑从头到尾捋一遍。这个系统解决的实际问题很清晰房东手里有多套房源租客有租房需求传统靠Excel和口头约定的管理方式信息不透明、合同易丢失、账单对不上。系统要做的事情就是——把房源信息、租客信息、合同签订、租金收缴、到期提醒这几件事全部线上化。适合的人群也很明确正在做JavaWeb课设/毕设的学生、想练手SSM整合的初级开发者、以及对后台管理系统开发流程感兴趣的人。读完这篇你不仅能复现这个项目还能搞清楚每一个技术选型背后的真正原因。1. 系统定位与整体设计思路1.1 这个系统到底解决了什么业务问题先别急着写代码我建议你把业务先理清楚。公寓出租的核心参与方就三个房东管理员、租客、房屋。围绕这三者衍生出的业务链路是房东录入房源 → 租客浏览/预约看房 → 双方签约 → 生成合同 → 定期缴纳租金 → 合同到期/续签。如果再加上管理视角你还需要统计报表——空置率、租金收入、租期分布这些数据管理者做决策时一定要看。很多学生做的出租系统表结构就建一张“房屋表”加一张“订单表”租客和房东共用一个用户表用个role字段区分。这样能跑通但逻辑上是有隐患的——如果一个租客同时也是房东他名下既有租赁记录又有出租记录这种简单的模型就处理不了。我的建议是用户表设计得稍微冗余一点用户表含角色 房东信息表 租客信息表分开哪怕实际开发时只用得到一张表这个拆分的意识要有答辩时能讲出道理。另外业务流程里容易忽略的是“状态流转”。一套房源不是简单的“已出租/未出租”两种状态它应该是空置 → 已预订待签约 → 已出租 → 已退租。你的代码里要有一个地方专门维护这个状态流转逻辑最好是用枚举类或常量类统一管理不要散落在各处if/else里。系统功能模块划分大致是这样的登录与权限管理员登录、租客注册登录、角色拦截房屋管理房源录入、修改、删除、上下架、条件搜索租客管理租客信息维护、租客历史租赁记录合同管理合同生成、合同到期提醒、续签操作账单管理租金记录、缴纳状态、逾期标记数据统计出租率、租金趋势、楼层/户型分布等可视化报表1.2 为什么是SSMJSPEasyUIECharts这套组合先解决一个很多人会问的问题为什么都2025年了还在用SSM和JSP答案很简单——不是因为它最新而是因为它在教学体系和中小型企业管理系统中依然占据很大份额。而且对于课设/毕设场景你追求的不是技术前沿而是稳定、可控、有话可讲。SSMSpring SpringMVC MyBatis这套组合Spring管对象SpringMVC管请求分发MyBatis管数据库操作三个框架各司其职。比起SpringBootSSM需要你手动配置的东西更多——web.xml、applicationContext.xml、springmvc.xml、mybatis-config.xml——但这恰恰是好事配置越多你对整个请求流转过程的理解就越深刻。面试时被问到“一次请求从浏览器到数据库再返回经历了哪些环节”做过SSM的人能脱口而出只用过SpringBoot的人反而容易卡壳。JSP在这个项目里承担的是视图层角色配合JSTL标签库渲染数据。选JSP而不是Freemarker或Thymeleaf一大原因是EasyUI和JSP配合极其顺滑。EasyUI是一套基于jQuery的后台UI框架表格、表单、弹窗、分页组件都是封装好的直接在JSP页面里引JS和CSS就能用不需要前后端分离不需要考虑跨域问题开发效率非常高。再加上ECharts做数据可视化图标渲染只需要在后端把统计好的JSON数据传到页面用echarts.init().setOption()就能出图。这套组合真正干起活来效率不低EasyUI的datagrid组件自带分页工具栏你只需要给后端传page和rows参数返回一个特定格式的JSON表格就自动渲染好了。这个开发体验做过的都懂真的上瘾。2. 数据库设计与核心表结构拆解2.1 MySQL数据库的三张核心表怎么设计MySQL在整个项目里是数据底座表结构设计得合理与否直接决定了后面代码写起来顺不顺畅。先说说最核心的几张表。用户表t_userid、username、password、phone、role0管理员/1租客/2房东、create_time。这里有一个容易忽略的点——密码不要明文存。推荐用MD5或BCrypt做哈希存储至少不要让数据库管理员一眼看到所有人密码。你如果只做课设MD5就够用了代码里加个DigestUtils.md5DigestAsHex()就是一行事。房屋表t_house这是整个系统的核心表。字段至少包括id、house_no房源编号、title标题、area面积、floor楼层、layout户型如两室一厅、rent月租金、deposit押金、status0空置/1已预订/2已出租/3已下架、description描述、cover_img封面图、create_time。这里要注意金额字段建议用DECIMAL而不是FLOAT/DOUBLE因为十进制浮点型在运算时会出现精度丢失的问题。DOUBLE(10,2)存99.9再拿出来可能是99.90000000000001这在租金计算里是硬伤。DECIMAL(10,2)就完全没有这个问题。合同表t_contractid、contract_no合同编号、house_id关联房屋、tenant_id关联租客、start_date、end_date、monthly_rent、status生效中/已到期/已解除、sign_time。这里加一个is_renewed 字段标记是否续签方便后续做“续签率”统计。这三张表怎么关联用户表和房屋表通过creator_id关联房东录入了哪些房源合同表和房屋表、用户表通过外键字段关联。但要注意物理外键我建议不要加逻辑外键就够了。物理外键在删数据时约束很强比如你要删一个租客他还有历史合同没有处理物理外键直接报错代码里你还得先处理依赖。用逻辑外键应用层控制数据的引用完整性开发灵活度更高。2.2 关于MySQL版本选择的个人建议从你给的搜索热词来看MySQL安装确实是很多人第一个卡住的地方——5.7版本、8.0版本、官网下载、压缩包版、rpm版、SSL连接错误、服务无法启动这些问题几乎人人都会遇到一遍。我给的建议非常明确如果做课设/毕设直接用MySQL 5.7。原因有三。第一5.7和SSM框架的兼容性经过了市场多年验证网上能搜到的SSM项目教程绝大多数基于5.7遇到问题搜答案最容易命中第二5.7的配置文件相对简单my.ini里基本只需要设置端口、字符集、存储引擎这几项就能跑起来第三8.0的密码加密方式改成了caching_sha2_password有时会遇到驱动版本不匹配导致连接失败的问题——虽然新版mysql-connector-java已经解决了但你还要注意驱动JAR版本多一个变量就多一个坑。当然如果你坚持装8.0只要注意两点一是用最新版的mysql-connector-java8.0.x版本二是在连接URL里显式加上useSSLfalse或者true并配证书和serverTimezoneAsia/Shanghai否则大概率报SSL连接错误或者时区错误。这两个问题我在第4部分会详细讲排查方法。3. 项目搭建与核心功能实现3.1 IDEA里快速跑起来一个SSM项目很多人下载了网上的SSM项目导入IDEA后编译报错、依赖下载失败、无法启动其实80%的问题出在环境配置上不是项目本身有问题。我把完整流程走一遍你照着做就能跑通。环境准备清单JDK 1.8不要用11以上SSM老项目容易出现兼容问题Maven 3.6.x3.8以上有些镜像源配置要注意Tomcat 8.5Tomcat 9/10对JSP支持有变化10以上的jakarta命名空间会直接把项目搞挂IDEA 2020版本以上即可MySQL 5.7 Navicat或命令行工具导入流程打开IDEA → File → New → Project from Existing Sources → 选择项目pom.xml → 选择Import Maven project → 等待依赖下载。这里提醒一句Maven的settings.xml一定要配置阿里云镜像不然下载依赖的速度会让你怀疑人生mirror idaliyunmaven/id mirrorOf*/mirrorOf name阿里云公共仓库/name urlhttps://maven.aliyun.com/repository/public/url /mirror数据库准备在Navicat里新建数据库db_house_rental字符集选utf8mb4然后导入项目自带的SQL脚本。如果没有脚本就手动执行上一步说的建表语句。改好jdbc.properties里的数据库连接jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/db_house_rental?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码Tomcat配置Run → Edit Configurations → 点 → Tomcat Server → Local → 在Deployment标签页点 → Artifact → 选择项目war包。如果不配置好Artifact启动时会报“No artifacts marked”这样的错误这就是很多人启动失败的直接原因。3.2 SSM整合的关键配置SSM三兄弟整合的过程必须手工做一遍才能理解整个请求的生命周期。我建议你不要偷懒用SpringBoot自动配置那一套亲手把三个配置文件写出来感受会完全不同。Spring配置文件applicationContext.xml作用范围是全局主要掌管数据源、事务管理器、MyBatis的SqlSessionFactory。context:component-scan base-packagecom.house context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan bean iddataSource classorg.springframework.jdbc.datasource.DriverManagerDataSource 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 nametypeAliasesPackage valuecom.house.entity/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.house.mapper/ /beanSpringMVC配置文件spring-mvc.xml只管Controller层开启注解驱动和视图解析器context:component-scan base-packagecom.house.controller use-default-filtersfalse context:include-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean mvc:annotation-driven/一个容易忽视的坑Spring容器和SpringMVC容器扫描的范围一定要分开Spring管Service/MapperSpringMVC管Controller。如果两边都装配了Service事务管理就会失效因为出现了两个代理对象这事排查起来很头疼。3.3 房屋管理模块EasyUI表格的增删改查怎么做房屋管理是整个系统体量最大的功能模块。页面上用的是EasyUI的datagrid我们要实现的效果是——页面加载后自动发起AJAX请求后端接口返回分页的JSON数据表格自动渲染点“添加”弹出一个dialog表单填写完成后提交保存每行后面有“编辑”和“删除”按钮。后端Controller核心就是一个分页查询接口加增删改的接口。EasyUI的datagrid在分页时会自动把当前页码和每页行数作为参数传过来参数名默认为page和rows所以Controller接收时只需要定义两个参数RequestMapping(/list) ResponseBody public MapString, Object houseList(Integer page, Integer rows, String keyword) { MapString, Object result new HashMap(); PageHelper.startPage(page, rows); // 用PageHelper插件分页 ListHouse houses houseService.queryHouseList(keyword); result.put(total, new PageInfo(houses).getTotal()); result.put(rows, houses); return result; }PageHelper是MyBatis的分页插件原理是在执行你写好的SQL前自动拼接一页LIMIT语句免去手写分页逻辑的麻烦。这里有一个使用禁忌——PageHelper.startPage() 后面必须紧跟着你的第一个MyBatis查询中间不能插任何其他SQL操作否则分页会生效到错误的查询上我当时就被这个坑害惨了查老半天才发现是顺序问题。保存房屋的前端AJAX代码大概是这样的$(#houseForm).form(submit, { url: ctx /house/save, onSubmit: function() { return $(this).form(validate); }, success: function(result) { var data JSON.parse(result); if (data.success) { $.messager.show({title: 提示, msg: 保存成功}); $(#houseTable).datagrid(reload); $(#houseDialog).dialog(close); } else { $.messager.alert(错误, data.msg, error); } } });EasyUI的表单提交是iframe形式的不是AJAX所以成功回调里的result是字符串一定要先JSON.parse再取属性。这个细节几乎所有新手都会踩我把它放在这希望你能避开。3.4 登录拦截与权限控制登录用Shiro还是用SpringMVC拦截器我的选择是——课设用拦截器就够了没必要给自己加戏上Shiro。Shiro的配置复杂度比拦截器高一个数量级对于出租系统这种只有“管理员/租客”两个角色的场景一个HandlerInterceptor完全能Hold住。写一个LoginInterceptorpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); Object user session.getAttribute(loginUser); if (user null) { // 判是否是AJAX请求 if (XMLHttpRequest.equals(request.getHeader(X-Requested-With))) { response.getWriter().write({\success\:false,\msg\:\未登录\}); } else { response.sendRedirect(request.getContextPath() /login.jsp); } return false; } return true; } }这里有一个我自己实际遇到的坑——网页端如果直接用EasyUI去操作未登录时后端如果返回的是重定向到login.jspEasyUI它并不会跟着跳转页面看起来是“没反应”。所以我在拦截器里做了区分如果是AJAX请求返回JSON告知未登录如果是普通页面请求做重定向。这样前端拿到JSON后自己弹窗提醒用户体验就对了。在spring-mvc.xml里注册拦截器排掉登录接口和静态资源mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/register/ mvc:exclude-mapping path/static/**/ bean classcom.house.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors注意如果拦截器把静态资源JS、CSS、图片也拦截了页面会变得异常丑而且很难排查到原因——因为浏览器控制台会一堆404或者failed to load resource。所以mvc:exclude-mapping里的静态资源路径一定要配全。3.5 ECharts报表统计模块的实现思路管理人员登录系统后看到的仪表盘页面上必须有几张图表——每月租金收入柱状图、房屋户型占比饼图、近六个月出租率折线图。ECharts的接入方式非常简单页面引入echarts.js后端提供查询统计接口把统计结果以JSON格式返回前端用ajax调接口把数据塞进图表的option里。以“租金收入按月统计”为例后端接口RequestMapping(/rentReport) ResponseBody public ListMapString, Object rentReport() { return houseService.getMonthlyRentReport(); }SQL语句在Mapper.xml里这样写select idgetMonthlyRentReport resultTypejava.util.Map SELECT DATE_FORMAT(pay_time, %Y-%m) AS months, SUM(amount) AS total_rent FROM t_payment WHERE pay_time DATE_SUB(CURDATE(), INTERVAL 6 MONTH) GROUP BY months ORDER BY months /select前端渲染代码$.ajax({ url: ctx /report/rentReport, dataType: json, success: function(data) { var chart echarts.init(document.getElementById(rentChart)); chart.setOption({ xAxis: { data: data.map(item item.months) }, yAxis: {}, series: [{ type: bar, data: data.map(item item.total_rent) }] }); } });你可能已经注意到了这里用了ES6的箭头函数。JSP页面里如果用的是老旧的Tomcat8和JDK8浏览器支持箭头函数没问题所以可以用。但如果你要兼容IE有些学校机房的老浏览器那就不能用了改成data.map(function(item) { return item.months; })保险。再说一个报错高发点——ECharts图表在页面上显示不出来多半是容器div没有高度。ECharts初始化时必须有明确的宽高仅设置width:100%而高度为0时图表就会渲染空。常见做法是给div定义固定高度比如styleheight:400px;。4. 部署与运行过程中的高频坑排查4.1 MySQL安装和初始化阶段的坑这个部分必须专门说因为从搜索热词来看绝大多数人第一个项目根本还没写成就死在MySQL安装上了。最经典的“net start mysql 服务无法启动”排查思路按顺序来第一步确认my.ini配置文件里basedir和datadir的路径是否正确路径里的斜杠建议用Unix风格/Windows反斜杠容易出现转义问题第二步以管理员身份打开CMD执行mysqld --initialize --console初始化数据目录如果这步没做过bin目录下根本没有data文件夹服务起来就报错第三步如果还是起不来去MySQL的data目录下看.err日志文件里面会写明具体原因比如端口3306被占用、权限不足、缺少某个依赖DLL。SSL连接错误是另一个高频问题。现象是项目启动时日志里报Establishing SSL connection without servers identity verification is not recommended或直接抛异常连不上。这个错误说的是——MySQL服务器开启了SSL但客户端的连接URL里没指定怎么处理。两种解决方式要么在连接URL后面加useSSLfalseserverTimezoneAsia/Shanghai要么在MySQL配置文件里把SSL关掉。注意serverTimezone也必须要加不然MySQL 8.0时区默认UTC会导致查出来的时间比实际早8小时。MySQL 5.7.44之后怎么不是5.7.45而是跳到8.0——这其实是因为5.7系列在5.7.44之后停止了更新维护官方将部分功能合并到了8.0版本继续演进。版本选择上不用纠结5.7.44是5.7系列的最终版反而是最稳定的。4.2 SSM项目启动失败的经典报错项目配置没问题了maven依赖也下了但Tomcat一启动就报错这种场景你们一定不陌生。我整理一下自己在开发过程中遇到的高频经典报错算是个速查表报错信息产生原因解决方式BeanCreationException: Error creating bean with name houseMapperMapper接口没有被扫描到或Mapper.xml的namespace写错检查MapperScannerConfigurer的basePackage和XML文件中的namespace是否符合全限定名规范Invalid bound statement (not found): com.house.mapper.HouseMapper.queryListMapper接口方法在XML里没对应上检查XML文件的id是否与接口方法名完全一致方法参数是否用Param指定了名称java.lang.NoClassDefFoundError: org/springframework/web/HttpRequestHandlerSpring版本和SpringMVC版本不一致或缺少Spring-web包在pom.xml里统一Spring全家桶版本号IllegalArgumentException: sessionFactory or jdbcTemplate is requiredSpring和MyBatis整合时SqlSessionFactory配置错误看applicationContext.xml里sqlSessionFactoryBean是否正确指向dataSourceError creating bean with name controller: Injection of autowired dependencies failedService注入失败通常是Service实现类没加Service检查Service实现类有没有标注注解而且扫描路径必须覆盖到4.3 JSP页面加载后刷新一次的实现热词里有个很有意思的问题jsp页面让加载完后刷新一次。这个需求听起来很奇怪但在实际项目里真的有——比如你在第一个页面里通过session设置了一些数据跳转后需要重新请求一次才能取到最新数据或者某些统计图表在初次渲染时位置错乱刷新一下才正常。解决方案不复杂在JSP页面的body最下方加一段script typetext/javascript if (window.name ! pageRefreshed) { window.name pageRefreshed; window.location.reload(true); } /script这个写法只会在第一次进入时刷新一次刷新后window.name变成了“pageRefreshed”下次就不会再刷新了。但慎用——刷新意味着页面上原有的表单输入内容会被清空如果用户正在填写长表单强制刷新会丢失数据。一般这个技巧只在特定场景下用比如页面初始化需要后端重新计算某些值或者ECharts图表初次渲染时容器尺寸异常。4.4 easyui表格加载失败怎么排查EasyUI datagrid加载不出来我在开发中归纳了三大类原因都是实战踩出来的第一类接口返回了错误格式。EasyUI的datagrid要求JSON必须是{total: 100, rows: [...]}这种固定结构。如果你后端返回的是List本身、或者Map的key拼写错了比如写成了totalCount表格就渲染不出来。用{ total:list.size(), rows: list }这种直接返回List的方式前端没有任何分页数据页面看起来就是空白表格。第二类字段名大小写问题。EasyUI的columns字段配置如果field名和JSON里的属性名对不上对应的列就是空的。比如JavaBean里属性是houseNoJSON输出就是houseNo但你在前端columns里写了field:house_no这一列就不显示。解决办法是给JavaBean属性加上 JsonProperty(house_no) 注解或者统一把数据库下划线命名在查询时用别名转成驼峰。第三类tomcat路径问题。前端AJAX请求地址没写对比如url: house/list但项目部署后web应用的上下文路径是/house_rental那这个请求就会404。所以代码里我习惯这样写url: %request.getContextPath()%/house/list或者在后端用${pageContext.request.contextPath}保证任何时候请求路径是对的。5. 这个项目还能往哪些方向扩展如果你做完了这个系统还有时间我个人建议至少考虑以下两个扩展方向对答辩加分非常明显。第一个方向引入支付模拟。目前租金收缴是“线下收款系统登记”可以扩展一个模拟支付的环节——在缴纳租金时模拟支付宝/微信的支付流程先生成订单再模拟回调修改支付状态然后对接ECharts做账单统计。这个扩展涉及的是“幂等性”和“回调通知”两个概念面试官很吃这一套。第二个方向做合同到期的批量提醒。具体的实现方式很简单——加一张定时任务表记录用户期望的预警提前天数比如合同到期前7天提醒然后Spring配置一个定时任务每天凌晨扫描一次数据库把即将到期的合同查出来在系统内生成一条待办消息。这种基于触发器的需求场景也是企业开发中的常见逻辑。6. 踩过足够多的坑之后我的一些体会这个项目做下来我个人最大的感受是——开发从来不是卡在代码难写上而是卡在环境搭建和隐蔽的配置问题上。你能看到的Java代码本身并不复杂Service调MapperController接收参数再调Service这套流程熟能生巧。但当一个项目同时涉及IDEA、Maven、Tomcat、MySQL、JSP、EasyUI六个环节时任何一个环节的配置错误都会让整个系统起不来——而这恰恰是SSM学习最大的价值它会逼着你把JavaWeb的整个链路都摸透而不是被SpringBoot包得严严实实、出了问题两眼一抹黑。如果你正在被这个项目折磨心态上的建议是——把报错当成朋友。Tomcat启动日志出现的第一行异常往往不是根源往下翻找到Caused by那一段那才是真正的病根。数据库连接不上看URL和密码Mapper找不到看namespace和id页面显示空白看控制台有没有报错信息、看Network里请求有没有发出去。所有的坑网上都能搜到所有的答案都在你的日志里。祝顺利跑通你的公寓出租系统——当Tomcat控制台出现那行熟悉的“Server startup in 5000 ms”时你会有一种“世界又转起来了”的感觉那种感觉真的很上瘾。