1. 房屋租赁管理系统开发概述
房屋租赁管理系统是典型的业务管理类软件,主要解决房东、中介机构在房源管理、租客信息、合同跟踪、费用结算等环节的信息化需求。采用Java SSM框架开发这类系统,能够充分发挥Java生态在业务系统开发中的成熟优势。
SSM框架组合(Spring+SpringMVC+MyBatis)是目前Java Web开发的主流技术栈之一,特别适合开发中小型业务管理系统。我在实际开发中发现,这套技术组合在开发效率、维护成本和性能表现上达到了很好的平衡点。相比传统的SSH框架,SSM更加轻量灵活;而对比Spring Boot全家桶方案,SSM又保留了更多手动配置的空间,适合需要深度定制的场景。
这个教程将带你完整实现一个具备以下核心功能的租赁管理系统:
- 房源信息管理(增删改查、状态变更)
- 租客信息管理(身份验证、历史记录)
- 合同生命周期管理(生成、签署、到期提醒)
- 租金收付跟踪(自动计算、逾期提醒)
- 数据统计与分析(空置率、收益统计)
提示:在实际商业项目中,建议将系统拆分为前台门户(面向租客)和后台管理(面向运营方)两个子系统。本教程为简化流程,先实现后台核心功能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境与工具准备
2.1 基础环境配置
开发Java Web项目首先需要配置好基础环境。我推荐使用以下组合:
- JDK 1.8(长期支持版本,兼容性最佳)
- Apache Maven 3.6+(依赖管理)
- MySQL 5.7(关系型数据库)
- Tomcat 8.5(应用服务器)
安装完成后需要验证环境变量配置正确。在命令行执行:
bash复制java -version
mvn -v
mysql --version
2.2 IDE选择与插件
IntelliJ IDEA Ultimate版是最适合Java Web开发的IDE,其对SSM框架的支持非常完善。社区版虽然免费,但缺少对JSP和Java EE的原生支持。关键插件包括:
- Lombok(简化POJO编写)
- MyBatisX(MyBatis映射文件辅助)
- Maven Helper(依赖分析)
- Alibaba Java Coding Guidelines(代码规范检查)
2.3 数据库设计要点
房屋租赁系统的数据库设计有几个特殊考虑点:
- 时间区间处理:合同期、付款周期等都需要存储时间范围
- 价格变更历史:租金可能会调整,需要保留历史记录
- 附件管理:合同扫描件、房产证等需要特殊存储方案
核心表结构设计示例:
sql复制CREATE TABLE `property` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`address` varchar(255) NOT NULL,
`area` decimal(10,2) DEFAULT NULL,
`monthly_rent` decimal(10,2) NOT NULL,
`status` enum('vacant','occupied','maintenance') DEFAULT 'vacant',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `contract` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`property_id` int(11) NOT NULL,
`tenant_id` int(11) NOT NULL,
`start_date` date NOT NULL,
`end_date` date NOT NULL,
`payment_day` tinyint(4) DEFAULT 1,
`deposit_amount` decimal(10,2) DEFAULT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`property_id`) REFERENCES `property` (`id`),
FOREIGN KEY (`tenant_id`) REFERENCES `tenant` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:实际项目中要考虑添加索引优化查询性能,特别是address、status等常用查询字段。
3. SSM框架整合实战
3.1 Maven依赖配置
pom.xml需要包含SSM核心依赖及常用工具库:
xml复制<dependencies>
<!-- Spring核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.18</version>
</dependency>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!-- MyBatis整合 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 数据库相关 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!-- 工具类 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
</dependencies>
3.2 Spring与MyBatis整合配置
applicationContext.xml关键配置项:
xml复制<!-- 数据源配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/rental_db?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="20"/>
</bean>
<!-- MyBatis SqlSessionFactory -->
<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.rental.model"/>
</bean>
<!-- Mapper接口扫描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.rental.mapper"/>
</bean>
<!-- 事务管理 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
3.3 Spring MVC配置要点
dispatcher-servlet.xml需要特别注意:
xml复制<!-- 注解驱动 -->
<mvc:annotation-driven>
<mvc:message-converters>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
<property name="objectMapper">
<bean class="com.fasterxml.jackson.databind.ObjectMapper">
<property name="dateFormat">
<bean class="java.text.SimpleDateFormat">
<constructor-arg type="java.lang.String" value="yyyy-MM-dd"/>
</bean>
</property>
</bean>
</property>
</bean>
</mvc:message-converters>
</mvc:annotation-driven>
<!-- 静态资源处理 -->
<mvc:resources mapping="/static/**" location="/static/"/>
<!-- 视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
<!-- 文件上传 -->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!-- 10MB -->
</bean>
4. 核心功能模块实现
4.1 房源管理模块
PropertyController示例代码:
java复制@Controller
@RequestMapping("/property")
public class PropertyController {
@Autowired
private PropertyMapper propertyMapper;
@GetMapping
public String list(Model model,
@RequestParam(defaultValue="1") int page,
@RequestParam(defaultValue="10") int size) {
PageHelper.startPage(page, size);
List<Property> properties = propertyMapper.selectAll();
PageInfo<Property> pageInfo = new PageInfo<>(properties);
model.addAttribute("pageInfo", pageInfo);
return "property/list";
}
@PostMapping
@ResponseBody
public Result add(@Valid Property property, BindingResult result) {
if (result.hasErrors()) {
return Result.error(result.getFieldError().getDefaultMessage());
}
propertyMapper.insert(property);
return Result.success();
}
@GetMapping("/{id}")
@ResponseBody
public Result detail(@PathVariable Integer id) {
Property property = propertyMapper.selectById(id);
return Result.success(property);
}
}
对应的MyBatis Mapper XML示例:
xml复制<mapper namespace="com.rental.mapper.PropertyMapper">
<resultMap id="BaseResultMap" type="Property">
<id column="id" property="id"/>
<result column="address" property="address"/>
<result column="area" property="area"/>
<result column="monthly_rent" property="monthlyRent"/>
<result column="status" property="status"/>
</resultMap>
<select id="selectAll" resultMap="BaseResultMap">
SELECT * FROM property
<where>
<if test="status != null">
AND status = #{status}
</if>
<if test="address != null and address != ''">
AND address LIKE CONCAT('%',#{address},'%')
</if>
</where>
ORDER BY id DESC
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO property(address, area, monthly_rent, status)
VALUES(#{address}, #{area}, #{monthlyRent}, #{status})
</insert>
</mapper>
4.2 合同管理特殊处理
合同管理有几个技术难点需要特别注意:
- 时间冲突校验:新签合同不能与同一房源现有合同时间重叠
java复制public boolean checkDateConflict(Integer propertyId, LocalDate start, LocalDate end) {
return contractMapper.countOverlapContracts(propertyId, start, end) > 0;
}
- 自动生成合同编号:建议采用"年+月+序列号"格式
java复制public String generateContractNo() {
String prefix = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMM"));
Integer maxSeq = contractMapper.selectMaxSeqByPrefix(prefix);
return prefix + String.format("%04d", (maxSeq == null ? 1 : maxSeq + 1));
}
- 租金计算逻辑:需要考虑起止日期不足整月的情况
java复制public BigDecimal calculateRent(LocalDate start, LocalDate end, BigDecimal monthlyRent) {
long months = ChronoUnit.MONTHS.between(start.withDayOfMonth(1), end.withDayOfMonth(1));
if (end.getDayOfMonth() > start.getDayOfMonth()) {
months++;
}
// 处理首月不足整月
int firstMonthDays = (int) ChronoUnit.DAYS.between(start,
start.plusMonths(1).withDayOfMonth(1));
BigDecimal firstMonthAmount = monthlyRent
.multiply(BigDecimal.valueOf(firstMonthDays))
.divide(BigDecimal.valueOf(start.lengthOfMonth()), 2, RoundingMode.HALF_UP);
// 完整月份
BigDecimal fullMonthsAmount = monthlyRent.multiply(BigDecimal.valueOf(months));
return firstMonthAmount.add(fullMonthsAmount);
}
5. 系统增强与优化
5.1 缓存策略实现
为提高系统性能,建议对以下数据添加缓存:
- 房源基础信息(变化频率低)
- 房源状态统计(计算成本高)
- 常用字典数据(地区、房间类型等)
Spring缓存配置示例:
java复制@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.disableCachingNullValues()
.serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
// 在Service层使用缓存
@Service
public class PropertyServiceImpl implements PropertyService {
@Cacheable(value = "property", key = "#id")
public Property getById(Integer id) {
return propertyMapper.selectById(id);
}
@CacheEvict(value = "property", key = "#property.id")
public void update(Property property) {
propertyMapper.update(property);
}
}
5.2 定时任务实现
租赁系统需要多种定时任务:
- 合同到期提醒(提前30天、7天)
- 租金逾期检查(付款日后3天未付)
- 数据备份(每日凌晨)
Spring定时任务配置:
java复制@Configuration
@EnableScheduling
public class ScheduleConfig {
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setThreadNamePrefix("rental-schedule-");
return scheduler;
}
}
@Component
public class ContractSchedule {
@Autowired
private ContractService contractService;
// 每天上午9点执行
@Scheduled(cron = "0 0 9 * * ?")
public void checkExpiringContracts() {
LocalDate date30 = LocalDate.now().plusDays(30);
LocalDate date7 = LocalDate.now().plusDays(7);
List<Contract> list30 = contractService.listExpiring(date30);
List<Contract> list7 = contractService.listExpiring(date7);
// 发送提醒逻辑...
}
// 每月5号检查逾期
@Scheduled(cron = "0 0 9 5 * ?")
public void checkOverduePayments() {
List<Payment> overdue = contractService.listOverduePayments();
// 处理逾期逻辑...
}
}
5.3 安全控制方案
基本安全措施应包括:
- 基于Spring Security的认证授权
- 敏感数据加密存储
- XSS和CSRF防护
Spring Security配置示例:
java复制@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/static/**").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/")
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.and()
.csrf().disable(); // 仅在开发环境禁用
http.headers()
.xssProtection()
.and()
.contentSecurityPolicy("script-src 'self'");
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
6. 常见问题与解决方案
6.1 MyBatis映射问题
问题1:查询结果字段为null
- 检查数据库字段名与POJO属性名是否一致
- 使用
<resultMap>明确指定映射关系 - 确认数据库连接参数添加了
useSSL=false&characterEncoding=utf8
问题2:动态SQL不生效
- 检查
<if>条件中的属性名是否正确 - 参数使用
@Param注解明确指定名称 - 确认OGNL表达式语法正确
6.2 事务失效场景
场景1:方法内部调用
java复制public void updateContract(Contract contract) {
updateStatus(contract); // 事务不会生效
}
@Transactional
public void updateStatus(Contract contract) {
// ...
}
解决方案:
- 将事务方法移到另一个Service类
- 通过AopContext获取代理对象调用
场景2:异常被捕获
java复制@Transactional
public void saveContract(Contract contract) {
try {
contractMapper.insert(contract);
} catch (Exception e) {
logger.error("保存失败", e); // 事务不会回滚
}
}
解决方案:
- 在catch块中抛出RuntimeException
- 添加
@Transactional(rollbackFor = Exception.class)
6.3 性能优化建议
-
数据库层面:
- 为常用查询条件添加索引
- 避免
SELECT *,只查询必要字段 - 大数据量表考虑分表分库
-
应用层面:
- 使用连接池并合理配置参数
- 批量操作使用MyBatis的
<foreach>或ExecutorType.BATCH - 缓存热点数据
-
前端层面:
- 分页查询默认限制每页条数
- 大数据量导出采用异步任务
- 静态资源启用浏览器缓存
7. 项目部署与运维
7.1 生产环境部署
推荐部署方案:
- JDK 1.8+
- Tomcat 8.5+(配置线程池和内存参数)
- Nginx作为反向代理(负载均衡、静态资源服务)
- MySQL主从配置(读写分离)
Tomcat优化配置(conf/server.xml):
xml复制<Connector port="8080" protocol="HTTP/1.1"
maxThreads="200"
minSpareThreads="20"
acceptCount="100"
connectionTimeout="20000"
redirectPort="8443" />
7.2 监控与日志
必备监控项:
- 应用健康状态(/actuator/health)
- 数据库连接池使用情况
- 接口响应时间
日志配置建议(logback-spring.xml):
xml复制<configuration>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.rental.mapper" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="FILE"/>
</root>
</configuration>
7.3 持续集成方案
推荐CI/CD流程:
- Git提交触发Jenkins构建
- Maven打包(跳过测试或使用测试容器)
- 静态代码检查(SonarQube)
- 部署到测试环境(Docker容器)
- 自动化测试(TestNG)
- 生产环境蓝绿部署
Jenkinsfile示例:
groovy复制pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'mvn clean package -DskipTests'
}
}
stage('Test') {
steps {
sh 'mvn test'
}
}
stage('Deploy') {
steps {
sshPublisher(
publishers: [
sshPublisherDesc(
configName: 'production-server',
transfers: [
sshTransfer(
sourceFiles: 'target/*.war',
removePrefix: 'target',
remoteDirectory: '/opt/tomcat/webapps'
)
],
execCommand: '''
/opt/tomcat/bin/shutdown.sh
sleep 5
/opt/tomcat/bin/startup.sh
'''
)
]
)
}
}
}
}
在开发这个系统的过程中,我发现最大的挑战不是技术实现,而是业务规则的准确建模。特别是租金计算、合同状态流转这些业务逻辑,稍有疏忽就会导致数据不一致。建议在开发前期花足够时间与业务方确认所有规则细节,最好能整理出完善的状态转换图和业务流程图。另外,MyBatis的动态SQL虽然强大,但过度使用会导致SQL难以维护,对于复杂查询建议适当使用存储过程或JPA的Criteria查询作为补充。
