☰
SSM商铺租赁管理系统开发实战:核心细节与避坑指南
2026/9/26 4:29:49 网站建设 项目流程

一个能跑通的SSM商铺租赁管理系统,开发完我只想说这三个细节

先说结论:如果你正在找Java课程设计或毕业设计的项目参考,基于SSM做的商铺租赁管理系统是一个选得比较稳的题目。它不冷门、不浮夸,业务逻辑清楚,开发量适中,调试文档和源码又刚好能把SSM全家桶的套路完整带一遍。

我自己做这套系统前后花了三周,每天早上打一遍完整日志,从建数据库到把系统部署到Tomcat,中间踩的坑不少,但最终交付的是可以直接演示的版本,附带的调试文档足够让一个没接触过SSM的人照着配通环境。这篇文章我会按实际做项目的顺序来写,重点放在“为什么这么设计”和“哪些地方容易挂”上,你照着做能少走很多弯路。

这套系统适合谁?适合正在做课程设计、需要交系统源码加说明文档的Java专业学生,也适合想通过一个完整项目快速熟悉Spring、SpringMVC、MyBatis整合流程的自学者。全文会用真实项目的视角来讲,所有模块和表结构都是按租赁业务的实际需求拆的,不是网上随便拼凑的demo。

1. 项目背景与技术选型:先搞清楚系统到底在管什么

1.1 商铺租赁的业务模型,比想象中要“脏”

商铺租赁管理系统看上去是个信息管理系统,实际上核心管的是三件事:商铺状态、钱、时间。商铺状态包括空置、已出租、待退租;钱包括押金、租金、水电杂费、违约金;时间包括合同起止日期、缴费周期、到期提醒时间。这三件事叠在一起,合同和租金台账就开始变得复杂了。

以我自己做的系统为例,商铺档案表里有一条商铺记录,关联到租赁合同表;租赁合同表又关联到租户表;租金明细表则跟着合同的缴费周期生成。如果一个商铺的历史新旧的合同都被保存在表里,那么“哪些铺面当前是空置的”这种问题就不能直接查商铺表,而是要去查合同表里有效的、尚未到期的那条记录。这一层“查状态要通过关联表”的逻辑,就是整套系统的关键所在,也是很多人把系统做成玩具demo的原因。

1.2 为什么还选SSM,而不是一上来就Spring Boot

现在新项目大多数都直接上Spring Boot了,身边同学也总问我,怎么还拿SSM做题目。我的理由很简单:既然做课程设计或毕业设计,重点不是做出一个东西,而是把框架的原理讲清楚。SSM由三个框架组成,Spring管对象和事务,SpringMVC管请求分发,MyBatis管数据库操作。你在SSM里调每一个完整请求,从浏览器到Controller再到Service再到Mapper,分层极其清楚,这对答辩和写论文都很有利。

Spring Boot虽然是主流,但很多配置都自动完成了,反而少了“搭建工程”这个过程。而我做这套系统时,光是通过手动配置spring、springmvc、mybatis三个配置文件,就把依赖注入、AOP事务、SQL映射的这些理解补了一遍。更实际一点,网上很多旧项目的源码和调试文档全是SSM结构的,你学会了SSM,再去看Spring Boot的项目,思路是平滑迁移的,但反过来不一定。

1.3 系统模块的切分:从需求到功能包的映射

我第一版踩了一个坑,就是上来直接写代码,结果写到第三天,Controller堆了十几个方法,Service层的类职责也不清晰。后来我重新按业务域把模块切分成了这几块:

  • 系统管理:用户登录、角色管理、菜单权限分配。
  • 商铺档案:商铺编号、铺面位置、面积、租金单价、当前状态。
  • 租户管理:姓名、电话、证件号、历史租赁记录。
  • 合同管理:新建合同、变更合同、退租办理、到期提醒。
  • 租金管理:应收租金生成、收款登记、退款、逾期记录。

对应Java源码里的包结构就是controller、service、mapper、entity、common,再加上JSP页面和静态资源。这个划分后面帮了大忙,因为写调试文档的时候,每个部分的功能点边界清楚,描述起来也很直白。

2. 关键表结构设计与状态流转:业务的根是表和状态

2.1 商铺表、合同表、租金表:三张主表和它们的关系

表结构设计好,系统就成功一半。我用的是MySQL 5.7,字符集用utf8mb4,排序规则用utf8mb4_general_ci。建表时,所有主键统一用自增int,时间字段用datetime,金额字段用decimal(10,2),避免double导致的精度问题。字段命名统一小写下划线风格,与Java属性驼峰一一对应。

商铺表shop_info的关键字段我列一下:

字段类型说明
idint主键自增
shop_novarchar(32)商铺编号,如SH-0102
shop_namevarchar(64)门牌或铺面名称
areadecimal(10,2)面积(平方米)
unit_pricedecimal(10,2)租金单价(元/平方米/月)
statustinyint0待租 1已租 2装修 3关闭

合同表contract_info则记录了租赁关系的生命周期,关键字段有合同编号、商铺ID、租户ID、起租日期、到期日期、押金金额、租金缴纳周期、合同状态。设计时要注意,合同表里冗余了商铺号和租户名两个字段,虽然不符合三范式,但是在列表展示时能少两次表连接,查询效率高一些,在小型系统里这个取舍完全合理。

租金表rent_record是系统最能体现价值的地方。每条记录包括合同ID、应收月份、应收日期、应收金额、实收金额、收款状态、收款时间。它必须能回答下面的问题:某个月份哪些商铺的租金没收到;某个租户是不是连续三个月逾期;活跃商铺的月度租金总收入是多少。

2.2 商铺状态不能直接更新,要通过合同来驱动

我第一版犯过的错误是,在商铺表里直接update状态字段。你从“待租”改成“已租”,表面上没问题,但一旦发生退租,你就要记一笔谁退的租,哪天退的,租金结算到哪天,这些信息全在合同表和租金表里。直接在商铺表改状态,历史记录就断了。

正确的做法是:签合同时生成一条status=1(生效中)的合同记录,并把商铺表的status改成已租;退租时把那笔合同的状态改成2(已退租),写退租时间和结算说明,再把商铺表改成待租。整个过程放在一个Service方法里,加上@Transactional,保证合同和商铺状态按同一事务提交。这套逻辑也是后期写调试文档和答辩时的重要素材。

2.3 到期提醒:用状态字段判断,不依赖定时任务

很多教程会让你上Quartz定时任务,对课程设计来说这不是最优方案,因为需要在服务器上配置定时策略,还原性差了不少。我的做法是在合同表里加一个is_notice字段,登录后的首页查出“即将在一个月内到期并且还没提醒过”的合同列表。管理员点了“已知晓”按钮后,这个字段就置为1,避免每次刷新都弹一样的提醒。

这样做的优点是逻辑透明、好讲、调试方便,缺点是它不是实时的,但商铺租赁管理场景本来就不需要秒级推送,这个折中完全够用。代码上就是一条带时间比较的SQL查询,由管理员主动去消费数据,也符合租赁业务的节奏。

3. SSM环境搭建与源码导入:照着调试文档走一遍还不够

3.1 开发环境版本的选择:一定用你能复盘的那一套

这一块可以说是我踩坑最多的环节。我的开发环境是JDK 1.8、Maven 3.6.3、Tomcat 8.5、MySQL 5.7,加上IDEA 2020.2。为什么强调版本?因为SSM项目对版本极度敏感,特别是Spring和MyBatis的兼容性。

pom.xml里我用的核心依赖版本是:spring 5.1.8.RELEASE,mybatis 3.5.3,mybatis-spring 2.0.3,mysql-connector-java 5.1.47(MySQL8要换8.0驱动),jackson-databind 2.9.9。调试文档里我特意把这份依赖清单写在了最前面,因为很多后续启动报错,本质上都是版本冲突。

3.2 三个配置文件的分工与核心配置

SSM总共有四个需要手动写的核心配置文件,缺一个启动就报错。

web.xml负责把SpringMVC的前端控制器配置进去,同时加载Spring的配置文件。这里有一个我经常看到的问题,就是Spring和SpringMVC的容器扫描重复,导致Service被初始化两次,事务失效。我的做法是:spring-mvc.xml只配置Controller层的扫描,applicationContext.xml配置Service、Mapper等除了Controller以外的扫描。

SpringMVC配置里,我开启了注解驱动、静态资源放行、视图解析器:

<context:component-scan base-package="com.shop.controller" /> <mvc:annotation-driven /> <mvc:resources mapping="/static/**" location="/static/" /> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/jsp/"></property> <property name="suffix" value=".jsp"></property> </bean>

MyBatis配置里,最关键的是把mapper接口包和XML文件的路径对应好,同时开启驼峰映射:

<configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>

数据库连接池我用的c3p0,配置文件里有一个特别容易错的地方:jdbcUrl必须带useUnicode=true&characterEncoding=utf8,否则后面页面显示中文全是问号。

3.3 导入源码的三个步骤:先看文档还是先跑起来

拿到源码后,如果源码目录里带了调试文档,我建议按这个顺序来做:

  1. 先看需求概览和数据库脚本,把sql文件导入MySQL,确认所有表都建好。
  2. 修改jdbc.properties里的账号密码,启动Tomcat,目标是先能登录系统。
  3. 登录成功后再翻功能模块,逐一和数据库表对应,不要一开始就深挖代码。

很多人喜欢先看代码,结果被各种类跳转到绕晕,信心受到打击。真正有效的方式是先把系统跑起来,再用调试文档里的菜单和SQL说明来做“回读”。跑起来之后,再去看Controller里的登录接口、Service里的业务逻辑,理解链条自然就顺了。

4. 业务逻辑代码怎么落地:Controller-Service-Mapper三层不能乱

4.1 登录与权限:会话管理和拦截器的处理方式

登录模块我使用的是传统Session方案,登录成功后把用户对象放进session,再写一个LoginInterceptor拦截所有/**请求,检查session里有没有用户标记。如果没登录,直接重定向到登录页。这是SSM项目里最常规的权限做法,也是调试文档里必讲的内容。

拦截器配置要注意顺序。我在springmvc.xml里配置了:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/static/**"/> <bean class="com.shop.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>

你一旦把静态资源的路径也拦截了,页面CSS全没了,新手调试半天找不到原因。所以排除路径必须写清楚,这是很小的点但非常普遍。

4.2 签合同与退租:事务管理和状态同步的写法

我拿新增合同举一个例子。Controller层接收表单数据后,组装成ContractInfo实体,调用contractService.addContract(contract)。这个方法里干了三件事:

  1. 往合同表插入新记录。
  2. 根据合同ID和起租日期,批量生成后续每个月的应收租金记录。
  3. 将对应的商铺表状态改成“已租”。

三步必须在同一个事务里,否则合同建好了,租金记录没生成,或者商铺状态没变,系统就是不一致状态。注解很直接:

@Override @Transactional(rollbackFor = Exception.class) public void addContract(ContractInfo contract) { contractMapper.insert(contract); List<RentRecord> list = RentRecordBuilder.build(contract); rentRecordMapper.batchInsert(list); shopInfoMapper.updateStatus(contract.getShopId(), 1); }

@Transactional(rollbackFor = Exception.class)是SSM里最常见的写法,如果只写@Transactional,默认只回滚RuntimeException,检查异常发生时事务不会回滚,这会让数据错得很隐蔽。

4.3 租金统计:用SQL完成聚合,别在Java里写循环

租金报表模块我踩过一次性能坑,最早是在Service层循环每个合同,再用Mapper查询租金金额,最后加总。数据量只有几百条的时候感觉不明显,一旦月租金明细上千条,一次报表请求要卡两三秒。

后面我把统计逻辑改成了SQL聚合查询:

SELECT contract_id, SUM(payable_amount) AS payable_total, SUM(IF(status = 1, paid_amount, 0)) AS paid_total FROM rent_record WHERE receivable_month BETWEEN #{startDate} AND #{endDate} GROUP BY contract_id

这样一次查询返回所有合同的应收实收汇总,Java层只做格式转换和页面展示。调试文档里我把这条SQL写成了一个“性能优化记录”,效果很直观。做项目时不要迷信Java里处理一切,数据库擅长的事就交给数据库。

4.4 前端页面与后端接口的数据交互

这套系统前端的核心页面包括登录页、商铺管理页、合同管理页、租金管理页、报表页。传统JSP模式下,列表页用Bootstrap做表格,分页用手写一个PageBean类,传递当前页码和总页数。

列表页接口我统一返回ModelAndView或Model,把分页参数和列表数据放进request域,JSP用JSTL的<c:forEach>循环渲染。但新增合同和收租登记这类操作,我用的是jQuery的ajax请求,后端返回JSON。这个时候必须在SpringMVC里配置JSON转换器,我用的是Jackson:

<mvc:annotation-driven> <mvc:message-converters> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/> </mvc:message-converters> </mvc:annotation-driven>

这个配置遗漏的话,ajax调接口会直接报406错误,或者返回字符串而不是JSON。这也是调试文档里必须单独写一条的错误提示。

5. 排查实录:调试文档里最值钱的几个坑

5.1 启动Tomcat报BeanCreationException,问题指向数据源

这个坑几乎每个SSM项目都会遇到。现象是Tomcat启动后,spring容器报Cannot get a connection, pool error ...,或者BeanCreationException指向sqlSessionFactory。我排查的过程分为三步:

先在MySQL命令行手动测试,确认账号密码和库名没问题。

再看jdbc.properties里的URL,结果发现写的是jdbc:mysql://localhost:3306/shop_rent,缺少时区参数和中文参数。MySQL 5.7一般不强制时区,但也得把useUnicode=true&characterEncoding=utf8加上。

最后查看本地MySQL服务是否真的启动了,Windows下有时MySQL服务是停止状态,启动服务再看就正常了。这三个步骤按顺序走一遍,90%的数据源问题都能解决。

5.2 白板页404:SpringMVC静态资源和视图路径对不上

页面找不到这种问题,我的经验是先看浏览器URL和后端Controller的RequestMapping是否完全一致。大小写不一致、多一个斜杠,都是常见原因。

还有一种问题是Controller方法返回字符串"admin/shopList",视图解析器会指向/WEB-INF/jsp/admin/shopList.jsp,如果你实际页面放在/WEB-INF/jsp/shopList.jsp,就会报404。检查视图解析器前缀和后缀与JSP实际目录之间的关系,是定位这个问题的唯一正确方式。我调试文档里专门放了一张前台页面结构树,就是为了让大家照着看目录。

5.3 ajax返回数据解析不了,前端报JSON parse error

这个坑出在很多新增和修改的窗口页面上。Controller返回一个对象,我本意是让Jackson转成JSON,但前端却解析失败。打开浏览器F12看到响应内容,发现是一串Java类名加对象引用,很明显就是没有走JSON转换器。

解决方式有两种,一是加上面说的MappingJackson2HttpMessageConverter,二是更直接的方式,在Controller方法上加@ResponseBody,并且保证引入了Jackson依赖。如果还不行,就把spring版本和jackson版本的兼容性也检查一遍,这里推荐spring 5.x配jackson 2.9.x,是最稳的组合。

5.4 页面中文全部变成问号:过滤器顺序和连接串都要查

这是最常规的中文乱码排查链路。先看JSP页面头部编码是否为UTF-8,再看web.xml里编码过滤器是不是配置在过滤器最前端。编码过滤器顺序应该放在第一位,然后在jdbc.properties里确认开启characterEncoding=utf8。三步检查完,乱码必然消失。

如果数据库表已经是utf8mb4,但仍然乱码,多半是连接串缺参数。UTF-8这个细节在调试文档里我记得反复强调过三遍,因为它不是一个账就能解决的,前后端和数据库任何一段断了都会表现成问号。

6. 从源码到验收:调试文档、LW与答辩怎么配合使用

6.1 源码到手后,调试文档应该像“游戏攻略”一样使用

我拿到一个自己不熟悉的SSM项目源码时,不会立刻把每个类都看懂,而是按下面的路线走一遍:

  • 先看数据库脚本:理解有哪些表、哪些关键字段。
  • 再启动项目:完成一次登录和一次查询,建立“系统真的在动”的直观感受。
  • 再看Controller层的路由:把每一个请求URL和页面菜单对应。
  • 最后看Service和Mapper:重点看带@Transactional的方法和多表SQL。

调试文档在我这套源码里,就是按这个思路写的。先把环境搭建步骤写清楚,然后分别描述每个模块的功能入口、对应表、关键代码位置,最后附上常见异常表格。这样即便基础一般的人,也能在半天内把系统跑通并理解核心逻辑。

6.2 LW(论文)部分怎么把项目讲得有技术含量

LW就是课程论文或毕业论文,很多同学系统做完了,但论文写得很虚。我的经验是,论文不要堆功能,要把“技术难点和你的解决方案”讲清楚。拿这套商铺租赁管理系统来说,可以重点写这几个问题:

  • 商铺状态与合同状态如何联动更新,为什么需要事务保证。
  • 租金应收记录如何批量生成,如何避免重复生成。
  • 列表分页和统计查询如何优化,用一条SQL替代循环。
  • 权限拦截器如何做到不收细节到静态资源。

论文里把“现状分析、可行性分析、系统设计、数据库设计、核心代码、测试结果”这个框架走一遍,再把上述四个问题作为“技术实现与解决思路”章节,内容自然就饱满,老师提问的时候也有内容可答。

6.3 答辩演示时最容易被问住的三个问题

我自己在演示测试时,专门模拟过评审可能会问的问题,大概归纳成三个:

第一个:如果同一间商铺签了多份合同,到期时间重叠怎么办?答案是在新增合同时校验该商铺当前状态是否为已出租,已出租则不能再进行签约。

第二个:租金漏收了怎么办?租金应收记录是签合同时就批量生成的,每笔记录有状态字段,未收是0,已收是1,偶尔漏单可以通过补录功能手工补,不破坏整体数据。

第三个:权限是怎么做的?登录用Session保存用户信息,拦截器检查请求是否需要登录,角色字段再决定菜单是否显示。这样回答出来,既有标准答案也有自己的细节,比只说“用了SSM”要有说服力得多。

7. 结尾:一些实在的开发建议

整套系统做下来,我最大的体会是,SSM商铺租赁管理系统的核心不在技术炫技,而在把租赁业务的每一个状态变更都落在一个可查询、可追溯的模型上。你如果只看代码,可能觉得没什么高深的东西,但当跳出来看整个项目,从数据库设计到事务处理到报表聚合,每一步都在解决真实业务问题。

如果你打算以这个题目作为课程设计或毕业设计,我给你的建议是不要跳过调试文档的记录过程。它不仅是给别人看的交付物,更是你自己复盘“这个项目为什么这么做”的最好工具。写文档的过程中,你会注意到很多写代码时没留意的细节,比如事务边界放在哪个方法上更合理,比如状态字段要不要加索引,这些思考恰恰是答辩时最从容的回答素材。

最后分享一个小技巧:把项目跑通之后,你可以在数据库里模拟一批异常数据,比如逾期三个月的租金记录、状态异常的商铺,然后专门做一个“数据处理”页面来修复它们。这个过程会让你对业务的理解更深一个层次,做出来的系统也不再只是增删改查,而是更像一个真正能被用起来的工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询