1. 项目概述与核心需求
这个基于SSM框架的房屋租赁系统是一个典型的JavaWeb应用,采用Maven进行依赖管理,前端使用JSP技术渲染页面。系统需要实现房源信息管理、租客管理、合同管理、支付记录等核心功能模块,同时要处理高并发的房源浏览和预约请求。
从技术架构来看,项目采用了经典的SSM(Spring+SpringMVC+MyBatis)组合:
- Spring负责IoC容器管理和事务控制
- SpringMVC处理Web层请求分发
- MyBatis作为ORM框架操作MySQL数据库
- JSP负责视图渲染
- Maven统一管理项目依赖
提示:虽然SSM框架现在逐渐被Spring Boot取代,但对于学习传统JavaWeb开发和企业遗留系统维护,掌握SSM技术栈仍然很有必要。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境搭建与配置
2.1 基础环境准备
开发这个系统需要以下环境配置:
- JDK 1.8+(推荐JDK 11 LTS版本)
- Apache Maven 3.6.3+
- MySQL 5.7+(推荐8.0版本)
- Tomcat 8.5+
- IntelliJ IDEA(或Eclipse with Maven插件)
在macOS上配置Maven环境:
bash复制# 下载Maven
wget https://archive.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz
# 解压到指定目录
tar -xzvf apache-maven-3.6.3-bin.tar.gz -C /usr/local/
# 配置环境变量
echo 'export MAVEN_HOME=/usr/local/apache-maven-3.6.3' >> ~/.zshrc
echo 'export PATH=$PATH:$MAVEN_HOME/bin' >> ~/.zshrc
source ~/.zshrc
# 验证安装
mvn -v
2.2 MySQL数据库配置
创建数据库和用户:
sql复制CREATE DATABASE house_rental DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'rental_user'@'%' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON house_rental.* TO 'rental_user'@'%';
FLUSH PRIVILEGES;
关键配置参数(my.cnf):
code复制[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
default-time-zone='+08:00'
innodb_buffer_pool_size=1G # 根据服务器内存调整
max_connections=200
3. 项目结构与Maven依赖
3.1 标准Maven项目结构
code复制house-rental-system/
├── src/
│ ├── main/
│ │ ├── java/ # Java源代码
│ │ │ └── com/
│ │ │ └── example/
│ │ │ ├── controller/ # SpringMVC控制器
│ │ │ ├── service/ # 业务服务层
│ │ │ ├── dao/ # 数据访问层
│ │ │ ├── entity/ # 实体类
│ │ │ └── util/ # 工具类
│ │ ├── resources/ # 资源文件
│ │ │ ├── spring/ # Spring配置文件
│ │ │ ├── mybatis/ # MyBatis映射文件
│ │ │ ├── db.properties # 数据库配置
│ │ │ └── log4j2.xml # 日志配置
│ │ └── webapp/ # Web资源
│ │ ├── WEB-INF/
│ │ │ ├── views/ # JSP文件
│ │ │ └── web.xml # Web配置
│ │ └── static/ # 静态资源
│ └── test/ # 测试代码
└── pom.xml # Maven项目配置
3.2 关键Maven依赖
pom.xml中的核心依赖配置:
xml复制<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<spring.version>5.2.8.RELEASE</spring.version>
<mybatis.version>3.5.6</mybatis.version>
<mybatis.spring.version>2.0.6</mybatis.spring.version>
</properties>
<dependencies>
<!-- Spring核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- MyBatis-Spring整合 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<!-- JSP支持 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jstl</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- 其他工具类 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.3</version>
</dependency>
</dependencies>
注意:在实际项目中,建议使用dependencyManagement统一管理依赖版本,避免版本冲突。
4. 数据库设计与实现
4.1 核心表结构设计
sql复制-- 房源信息表
CREATE TABLE `house` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '房源ID',
`title` varchar(100) NOT NULL COMMENT '房源标题',
`address` varchar(200) NOT NULL COMMENT '详细地址',
`price` decimal(10,2) NOT NULL COMMENT '月租金',
`area` decimal(6,2) NOT NULL COMMENT '面积(平方米)',
`room_count` tinyint(4) NOT NULL COMMENT '卧室数量',
`hall_count` tinyint(4) NOT NULL DEFAULT '0' COMMENT '客厅数量',
`bathroom_count` tinyint(4) NOT NULL DEFAULT '1' COMMENT '卫生间数量',
`floor` smallint(6) NOT NULL COMMENT '所在楼层',
`total_floor` smallint(6) NOT NULL COMMENT '总楼层',
`description` text COMMENT '房源描述',
`main_image` varchar(255) DEFAULT NULL COMMENT '主图URL',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态(1-可租 2-已租 3-下架)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `idx_status` (`status`),
KEY `idx_price` (`price`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='房源信息表';
-- 用户表
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) NOT NULL COMMENT '用户名',
`password` varchar(100) NOT NULL COMMENT '密码(加密存储)',
`real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
`phone` varchar(20) NOT NULL COMMENT '手机号',
`id_card` varchar(18) DEFAULT NULL COMMENT '身份证号',
`type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '用户类型(1-租客 2-房东 3-管理员)',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态(1-正常 2-禁用)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_username` (`username`),
UNIQUE KEY `uk_phone` (`phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 租赁合同表
CREATE TABLE `contract` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '合同ID',
`house_id` bigint(20) NOT NULL COMMENT '房源ID',
`tenant_id` bigint(20) NOT NULL COMMENT '租客ID',
`landlord_id` bigint(20) NOT NULL COMMENT '房东ID',
`start_date` date NOT NULL COMMENT '开始日期',
`end_date` date NOT NULL COMMENT '结束日期',
`monthly_rent` decimal(10,2) NOT NULL COMMENT '月租金',
`deposit` decimal(10,2) NOT NULL COMMENT '押金',
`payment_cycle` tinyint(4) NOT NULL DEFAULT '1' COMMENT '付款周期(1-月付 3-季付 6-半年付 12-年付)',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态(1-生效中 2-已到期 3-提前终止)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_house` (`house_id`),
KEY `idx_tenant` (`tenant_id`),
KEY `idx_landlord` (`landlord_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租赁合同表';
-- 支付记录表
CREATE TABLE `payment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '支付ID',
`contract_id` bigint(20) NOT NULL COMMENT '合同ID',
`amount` decimal(10,2) NOT NULL COMMENT '支付金额',
`payment_date` date NOT NULL COMMENT '支付日期',
`period_start` date NOT NULL COMMENT '账期开始',
`period_end` date NOT NULL COMMENT '账期结束',
`payment_method` tinyint(4) NOT NULL DEFAULT '1' COMMENT '支付方式(1-支付宝 2-微信 3-银行转账)',
`status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态(1-待支付 2-已支付 3-已取消)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_contract` (`contract_id`),
KEY `idx_payment_date` (`payment_date`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='支付记录表';
4.2 MyBatis映射文件示例
HouseMapper.xml配置:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.dao.HouseMapper">
<resultMap id="BaseResultMap" type="com.example.entity.House">
<id column="id" property="id" jdbcType="BIGINT"/>
<result column="title" property="title" jdbcType="VARCHAR"/>
<result column="address" property="address" jdbcType="VARCHAR"/>
<result column="price" property="price" jdbcType="DECIMAL"/>
<result column="area" property="area" jdbcType="DECIMAL"/>
<!-- 其他字段映射 -->
</resultMap>
<sql id="Base_Column_List">
id, title, address, price, area, room_count, hall_count, bathroom_count,
floor, total_floor, description, main_image, status, create_time, update_time
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">
SELECT
<include refid="Base_Column_List"/>
FROM house
WHERE id = #{id,jdbcType=BIGINT}
</select>
<select id="selectByCondition" resultMap="BaseResultMap" parameterType="map">
SELECT
<include refid="Base_Column_List"/>
FROM house
WHERE status = 1
<if test="minPrice != null">
AND price >= #{minPrice}
</if>
<if test="maxPrice != null">
AND price <= #{maxPrice}
</if>
<if test="roomCount != null">
AND room_count = #{roomCount}
</if>
<if test="areaId != null">
AND address LIKE CONCAT(#{areaId}, '%')
</if>
ORDER BY
<choose>
<when test="sortField == 'price'">price ${sortOrder}</when>
<when test="sortField == 'createTime'">create_time ${sortOrder}</when>
<otherwise>id DESC</otherwise>
</choose>
LIMIT #{offset}, #{pageSize}
</select>
<insert id="insert" parameterType="com.example.entity.House" useGeneratedKeys="true" keyProperty="id">
INSERT INTO house (
title, address, price, area, room_count, hall_count,
bathroom_count, floor, total_floor, description, main_image, status
) VALUES (
#{title,jdbcType=VARCHAR}, #{address,jdbcType=VARCHAR},
#{price,jdbcType=DECIMAL}, #{area,jdbcType=DECIMAL},
#{roomCount,jdbcType=TINYINT}, #{hallCount,jdbcType=TINYINT},
#{bathroomCount,jdbcType=TINYINT}, #{floor,jdbcType=SMALLINT},
#{totalFloor,jdbcType=SMALLINT}, #{description,jdbcType=VARCHAR},
#{mainImage,jdbcType=VARCHAR}, #{status,jdbcType=TINYINT}
)
</insert>
<!-- 其他CRUD操作 -->
</mapper>
5. 核心功能实现
5.1 房源搜索功能
Service层实现:
java复制@Service
public class HouseServiceImpl implements HouseService {
@Autowired
private HouseMapper houseMapper;
@Override
public PageInfo<House> searchHouses(HouseSearchCondition condition, int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
Map<String, Object> params = new HashMap<>();
params.put("minPrice", condition.getMinPrice());
params.put("maxPrice", condition.getMaxPrice());
params.put("roomCount", condition.getRoomCount());
params.put("areaId", condition.getAreaId());
params.put("sortField", condition.getSortField());
params.put("sortOrder", condition.getSortOrder());
// 计算分页偏移量
int offset = (pageNum - 1) * pageSize;
params.put("offset", offset);
params.put("pageSize", pageSize);
List<House> houses = houseMapper.selectByCondition(params);
return new PageInfo<>(houses);
}
// 其他服务方法...
}
Controller层实现:
java复制@Controller
@RequestMapping("/house")
public class HouseController {
@Autowired
private HouseService houseService;
@RequestMapping(value = "/search", method = RequestMethod.GET)
public String search(
@ModelAttribute HouseSearchCondition condition,
@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize,
Model model) {
PageInfo<House> pageInfo = houseService.searchHouses(condition, pageNum, pageSize);
model.addAttribute("pageInfo", pageInfo);
model.addAttribute("condition", condition);
return "house/list";
}
// 其他控制器方法...
}
5.2 合同管理功能
合同创建服务实现:
java复制@Service
@Transactional
public class ContractServiceImpl implements ContractService {
@Autowired
private ContractMapper contractMapper;
@Autowired
private HouseMapper houseMapper;
@Autowired
private PaymentService paymentService;
@Override
public void createContract(Contract contract) {
// 验证房源状态
House house = houseMapper.selectByPrimaryKey(contract.getHouseId());
if (house == null || house.getStatus() != HouseStatus.AVAILABLE.getCode()) {
throw new BusinessException("房源不可用");
}
// 验证日期有效性
if (contract.getStartDate().isAfter(contract.getEndDate())) {
throw new BusinessException("结束日期不能早于开始日期");
}
// 保存合同
contract.setStatus(ContractStatus.ACTIVE.getCode());
contractMapper.insert(contract);
// 更新房源状态
house.setStatus(HouseStatus.RENTED.getCode());
houseMapper.updateByPrimaryKey(house);
// 生成首期支付记录
Payment firstPayment = new Payment();
firstPayment.setContractId(contract.getId());
firstPayment.setAmount(contract.getMonthlyRent().add(contract.getDeposit()));
firstPayment.setPaymentDate(LocalDate.now());
firstPayment.setPeriodStart(contract.getStartDate());
firstPayment.setPeriodEnd(contract.getStartDate().plusMonths(1).minusDays(1));
firstPayment.setPaymentMethod(PaymentMethod.BANK_TRANSFER.getCode());
firstPayment.setStatus(PaymentStatus.PENDING.getCode());
paymentService.createPayment(firstPayment);
}
// 其他合同相关方法...
}
5.3 支付功能集成
支付宝支付集成示例:
java复制@Service
public class AlipayServiceImpl implements PaymentGatewayService {
@Value("${alipay.appId}")
private String appId;
@Value("${alipay.privateKey}")
private String privateKey;
@Value("${alipay.publicKey}")
private String publicKey;
@Value("${alipay.notifyUrl}")
private String notifyUrl;
@Value("${alipay.returnUrl}")
private String returnUrl;
@Override
public String createPayment(Payment payment, String subject) {
// 实例化客户端
AlipayClient alipayClient = new DefaultAlipayClient(
"https://openapi.alipay.com/gateway.do",
appId,
privateKey,
"json",
"UTF-8",
publicKey,
"RSA2");
// 创建API对应的request
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
// 组装业务参数
JSONObject bizContent = new JSONObject();
bizContent.put("out_trade_no", payment.getId().toString());
bizContent.put("total_amount", payment.getAmount().toString());
bizContent.put("subject", subject);
bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
request.setBizContent(bizContent.toString());
try {
// 调用SDK生成表单
String form = alipayClient.pageExecute(request).getBody();
return form;
} catch (AlipayApiException e) {
throw new PaymentException("支付宝支付创建失败", e);
}
}
@Override
public boolean verifyNotification(Map<String, String> params) {
try {
return AlipaySignature.rsaCheckV1(
params,
publicKey,
"UTF-8",
"RSA2");
} catch (AlipayApiException e) {
throw new PaymentException("支付宝通知验证失败", e);
}
}
}
6. 系统安全与性能优化
6.1 安全防护措施
-
SQL注入防护:
- 使用MyBatis的参数绑定方式
- 禁止拼接SQL语句
- 使用拦截器对参数进行过滤
-
XSS防护:
- JSP中使用JSTL的
<c:out>标签输出内容 - 实现XSS过滤器:
java复制public class XssFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response); } // 其他方法... } public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper { public XssHttpServletRequestWrapper(HttpServletRequest request) { super(request); } @Override public String getParameter(String name) { return cleanXSS(super.getParameter(name)); } @Override public String[] getParameterValues(String name) { String[] values = super.getParameterValues(name); if (values == null) return null; String[] cleanedValues = new String[values.length]; for (int i = 0; i < values.length; i++) { cleanedValues[i] = cleanXSS(values[i]); } return cleanedValues; } private String cleanXSS(String value) { if (value == null) return null; value = value.replaceAll("<", "<").replaceAll(">", ">"); value = value.replaceAll("\\(", "(").replaceAll("\\)", ")"); value = value.replaceAll("'", "'"); value = value.replaceAll("eval\\((.*)\\)", ""); value = value.replaceAll("[\\\"\\\'][\\s]*javascript:(.*)[\\\"\\\']", "\"\""); return value; } } - JSP中使用JSTL的
-
密码安全:
- 使用BCrypt加密存储密码
- 实现密码加密工具类:
java复制public class PasswordUtil { private static final int BCRYPT_STRENGTH = 12; public static String encrypt(String rawPassword) { return BCrypt.hashpw(rawPassword, BCrypt.gensalt(BCRYPT_STRENGTH)); } public static boolean matches(String rawPassword, String encodedPassword) { return BCrypt.checkpw(rawPassword, encodedPassword); } }
6.2 性能优化策略
-
数据库优化:
- 合理设计索引
- 使用连接池(如HikariCP)
- 配置MyBatis二级缓存
-
缓存策略:
- 使用Redis缓存热门房源
- 实现缓存服务:
java复制@Service public class RedisCacheServiceImpl implements CacheService { @Autowired private RedisTemplate<String, Object> redisTemplate; private static final String HOUSE_KEY_PREFIX = "house:"; private static final long HOUSE_EXPIRE_SECONDS = 3600; // 1小时 @Override public House getHouseFromCache(Long id) { String key = HOUSE_KEY_PREFIX + id; return (House) redisTemplate.opsForValue().get(key); } @Override public void cacheHouse(House house) { if (house == null || house.getId() == null) return; String key = HOUSE_KEY_PREFIX + house.getId(); redisTemplate.opsForValue().set(key, house, HOUSE_EXPIRE_SECONDS, TimeUnit.SECONDS); } @Override public void evictHouseCache(Long id) { String key = HOUSE_KEY_PREFIX + id; redisTemplate.delete(key); } } -
静态资源优化:
- 启用Gzip压缩
- 配置浏览器缓存
- 使用CDN加速
-
分页查询优化:
- 使用PageHelper实现物理分页
- 避免使用
select * - 优化count查询
7. 部署与运维
7.1 项目打包与部署
-
Maven打包:
bash复制mvn clean package -Dmaven.test.skip=true -
Tomcat部署:
- 将生成的WAR包复制到Tomcat的webapps目录
- 配置server.xml的Context:
xml复制<Context docBase="/path/to/house-rental-system.war" path="/rental" reloadable="false"> <Environment name="spring.profiles.active" value="prod" type="java.lang.String"/> </Context> -
数据库连接池配置(applicationContext.xml):
xml复制<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/house_rental?useSSL=false&serverTimezone=Asia/Shanghai"/> <property name="username" value="rental_user"/> <property name="password" value="StrongPassword123!"/> <property name="maximumPoolSize" value="20"/> <property name="minimumIdle" value="5"/> <property name="idleTimeout" value="30000"/> <property name="connectionTimeout" value="30000"/> <property name="maxLifetime" value="1800000"/> <property name="connectionTestQuery" value="SELECT 1"/> </bean>
7.2 日志配置
log4j2.xml配置示例:
xml复制<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
<RollingFile name="File" fileName="logs/app.log"
filePattern="logs/app-%d{yyyy-MM-dd}.log.gz">
<PatternLayout>
<Pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy interval="1" modulate="true"/>
</Policies>
<DefaultRolloverStrategy max="30"/>
</RollingFile>
</Appenders>
<Loggers>
<Logger name="com.example" level="debug" additivity="false">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
<AppenderRef ref="File"/>
</Root>
</Loggers>
</Configuration>
7.3 监控与维护
-
健康检查接口:
java复制@RestController @RequestMapping("/monitor") public class MonitorController { @Autowired private DataSource dataSource; @GetMapping("/health") public ResponseEntity<Map<String, Object>> healthCheck() { Map<String, Object> result = new HashMap<>(); // 检查数据库连接 try (Connection conn = dataSource.getConnection()) { result.put("database", "UP"); } catch (SQLException e) { result.put("database", "DOWN"); } // 检查内存状态 Runtime runtime = Runtime.getRuntime(); result.put("memory", new HashMap<String, Object>() {{ put("total", runtime.totalMemory()); put("free", runtime.freeMemory()); put("max", runtime.maxMemory()); }}); return ResponseEntity.ok(result); } } -
定时任务:
- 合同到期提醒
- 支付逾期检查
java复制@Component public class ContractCheckTask { @Autowired private ContractService contractService; @Scheduled(cron = "0 0 9 * * ?") // 每天上午9点执行 public void checkExpiringContracts() { List<Contract> contracts = contractService.findContractsExpiringIn(7); // 7天内到期 for (Contract contract : contracts) { // 发送提醒通知 sendReminder(contract); } } private void sendReminder(Contract contract) { // 实现通知逻辑 } }
8. 常见问题与解决方案
8.1 中文乱码问题
问题表现:
- 页面显示乱码
- 表单提交后数据乱码
- 数据库存储乱码
解决方案:
-
确保所有JSP页面添加编码声明:
jsp复制<%@ page contentType="text/html;charset=UTF-8" language="java" %> -
配置字符编码过滤器(web.xml):
xml复制<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> -
确保MySQL连接字符串指定字符集:
code复制jdbc:mysql://localhost:3306/house_rental?useUnicode=true&characterEncoding=UTF-8
8.2 MyBatis查询结果映射问题
问题表现:
- 查询结果字段为null
- 日期类型转换异常
- 一对一、一对多关联查询失败
解决方案:
- 确保实体类属性名与数据库字段名一致,或使用
@Column注解指定映射 - 日期类型处理:
xml复制<result column="create_time" property="createTime" jdbcType="TIMESTAMP"/> - 复杂关联查询使用
<association>和<collection>:xml复制<resultMap id="ContractDetailMap" type="com.example.vo.ContractDetailVO"> <id column="id" property="id"/> <!-- 其他字段映射 --> <association property="house" javaType="com.example.entity.House"> <id column="house_id" property="id"/> <result column="house_title" property="title"/> <!-- 其他房源字段 --> </association> <association property="tenant" javaType="com.example.entity.User"> <id column="tenant_id" property="id"/> <result column="tenant_name" property="realName"/> <!-- 其他租客字段 --> </association> </resultMap>
8.3 事务管理问题
问题表现:
- 事务不生效
- 异常不回滚
- 事务传播行为不符合预期
解决方案:
-
确保在Spring配置中启用注解驱动的事务管理:
xml复制<tx:annotation-driven transaction-manager="transactionManager"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> -
正确使用
@Transactional注解:java复制@Service public class PaymentServiceImpl implements PaymentService { @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED) @Override public void processPayment(Payment payment) { // 业务逻辑 } } -
注意事务方法的调用方式:
- 事务方法必须通过代理调用(即从Spring容器获取的Bean调用)
- 同类中方法互相调用不会触发事务
9. 项目扩展与进阶
9.1 微服务架构改造
随着业务发展,可以考虑将单体应用拆分为微服务:
-
服务拆分:
- 用户服务
- 房源服务
- 合同服务
- 支付服务
- 通知服务
-
技术选型:
- Spring Cloud Alibaba
- Nacos服务发现与配置中心
- Sentinel流量控制
- Seata分布式事务
-
API网关:
- Spring Cloud Gateway
- 统一认证授权
- 请求路由与限流
9.2 前端技术升级
-
前后端分离:
- 使用Vue.js/React重构前端
- RESTful API设计
- JWT认证
-
移动端适配:
- 开发微信小程序
- 响应式Web设计
- APP原生封装
9.3 数据分析与可视化
-
数据仓库:
- 使用Apache Doris构建实时数仓
- 房源分析
- 租客行为分析
-
BI工具集成:
- 集成Superset或Metabase
- 租金收入报表
- 空置率分析
-
推荐系统:
- 基于用户行为的房源推荐
- 协同过滤算法
- 实时推荐引擎
10. 开发心得与建议
在实际开发这个房屋租赁系统的过程中,我总结了以下几点经验:
-
数据库设计先行:
- 良好的数据库设计是系统稳定性的基础
- 提前考虑扩展性,如分表策略
- 建立合理的索引,但避免过度索引
-
分层架构清晰:
- 严格遵循Controller-Service-DAO分层
- 业务逻辑集中在Service层
- DAO层只负责数据访问
-
异常处理统一:
- 定义业务异常体系
- 全局异常处理器统一处理
- 友好的错误信息返回
-
测试驱动开发:
- 编写单元测试覆盖核心逻辑
- 使用Mockito模拟依赖
- 集成测试验证完整流程
-
文档及时更新:
- API文档使用Swagger生成
- 数据库变更记录维护
