简介:这份Java+Swing+MySQL房屋租赁管理系统项目,完整包含前端界面、后端逻辑、数据库脚本和课程设计文档,主要面向Java初学者、数据库课程设计或毕业设计需要完整案例的读者。系统功能覆盖前台浏览与用户注册登录、后台管理员登录、房源信息录入修改删除查询,并支持按分类或多条件搜索房源,方便租客快速查看房型、价格和房主等详细内容。压缩包共29个文件,以11个Java源码和11个class编译文件为核心,数据库方面附有house_rental.sql,项目配置含.project、.classpath和prefs等Eclipse环境文件,另提供jar运行库及docx格式的《Java高级程序设计》课程设计文档,整体约3.89MB,导入IDE后即可对照学习。源码、SQL和文档配套完整,读者既能运行系统了解Swing控件与MySQL增删改查的关联过程,也能参考其用户管理、房源管理和查找模块来改造扩展自己的课程作业或毕业设计。目前已吸引342人学习下载,适合作为课设起步、功能完善和文档撰写的直接参考。
1. 房屋租赁管理系统:一套能跑的 Swing + MySQL 工程,值得下
做课程设计或者给小型中介做内部工具时,最尴尬的不是需求复杂,而是资料搜了一堆,打开全是半成品——数据库脚本缺失、Swing 界面和 MVC 分层糊在一起、跑起来就报错。这套 Java + Swing + MySQL 实现的房屋租赁管理系统,属于少见的"下载即闭环"类型:源码能编译、SQL 能建库、文档能对上号。它解决的是房源、租客、合同、收租这四条业务线在单机桌面环境下的数据管理问题,适合正在做课程设计的学生、想练手完整 CRUD 的 Java 新人,也适合需要快速搭一套内部管理工具的小团队。
2. 系统架构与数据库设计:先把表结构摸清再改代码
资源下载下来,别急着点运行。先花半小时把工程目录和数据库脚本过一遍,你后面改代码能少踩一半的坑。Swing 工程的结构通常很直白:view包放窗口和面板,model包放实体类,dao或util包放 JDBC 操作,入口一般是MainFrame或LoginFrame。
2.1 为什么是这个组合:Swing 桌面端 + MySQL 存储
先说选型逻辑。同样是"房屋租赁管理系统",你可以做成 Spring Boot + Vue 的 Web 项目,也可以做成 Swing + MySQL 的桌面项目,但两者的适用场景完全不同。Swing 方案的核心优势是单机部署、零前后端联调成本:一个进程里完成界面渲染和数据访问,用户装好 JDK 和 MySQL 就能直接跑,不需要配置 Tomcat,不需要处理跨域,不需要考虑浏览器兼容性。
MySQL 在这套组合里承担的是数据持久化职责。相比直接用文件存储或者 Derby 这类内嵌数据库,MySQL 的好处在于:SQL 语法通用,表结构设计能直接迁移到其他技术栈;支持多表关联查询,合同和账单这类业务刚好需要 JOIN;课程设计答辩时,面试官或老师一定会问表结构设计,用 MySQL 设计过的同学能说清楚外键和索引,这在 Java 面试里属于高频考点。
另外,这套系统没有引入 MyBatis 或 Hibernate,而是直接用 JDBC 操作 SQL,这不是偷懒,反而对新手更友好。框架会把 SQL 细节藏起来,但 JDBC 会让你看清楚每一次查询是怎么发出、怎么取回的,排查问题时思路更直接。
2.2 五张核心表:业务字段怎么落到关系模型
房屋租赁系统的核心业务,本质上是三个实体加两个关联过程:房源、租客是基础档案,合同把房源和租客绑到一起,账单记录每次收租。所以数据库里最合理的方案是五张表:用户表、房屋表、租客表、合同表、账单表。
用户表负责登录认证,字段不需要多,但用户名必须唯一。房屋表要记录房号、地址、面积、月租金、押金和当前状态,其中状态字段是业务流转的关键。租客表存姓名、电话、身份证号、紧急联系人。合同表是系统的枢纽,它同时持有房屋 ID 和租客 ID,外加起止日期、合同价、押金。账单表记录每一期收租情况,按合同 ID 关联。
| 表名 | 核心字段 | 作用 |
|---|---|---|
| t_user | username, password, role | 登录与权限控制 |
| t_house | house_no, address, rent_price, status | 房源档案,status 控制空置/已租/预订 |
| t_tenant | name, phone, id_card | 租客档案 |
| t_contract | house_id, tenant_id, start_date, end_date | 房源与租客的关联绑定 |
| t_bill | contract_id, period, amount, status | 收租台账,可按合同追溯 |
这里最值得关注的是status字段。房源表里用0-空置 1-已租 2-预订表示状态,合同表里用1-执行中 2-已到期表示合同状态,这两个状态在签约、退租时会联动修改。新手容易犯的错误是把状态做成字符串直接存,比如"空置""已租",看似直观,但后续写 SQL 统计"还有几套房可租"时,字符串比较效率低且容易写错,整数枚举才是常见做法。
2.3 初始化 SQL 脚本:建库、建表、造测试数据
下载包里的 SQL 脚本一般会包含建库语句、建表语句和测试数据三个部分。我建议你把脚本从头到尾执行一遍,执行成功后立刻用 Navicat 或命令行查看表结构,确认字段类型与你预期一致再往下走。
CREATE DATABASE IF NOT EXISTS house_rental DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE house_rental; -- 用户表:登录账号,密码字段建议存 MD5,不要存明文 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50), role TINYINT DEFAULT 1 COMMENT '1-管理员 2-操作员', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 房屋表:状态字段用整数枚举,方便统计 CREATE TABLE t_house ( id INT PRIMARY KEY AUTO_INCREMENT, house_no VARCHAR(20) NOT NULL, address VARCHAR(200) NOT NULL, area DECIMAL(8,2), rent_price DECIMAL(10,2) NOT NULL COMMENT '月租金', deposit DECIMAL(10,2), status TINYINT DEFAULT 0 COMMENT '0-空置 1-已租 2-预订', remark VARCHAR(200) );这段脚本里有几个参数值得注意。utf8mb4是 MySQL 8.0 下推荐使用的字符集,相比老旧的utf8,它能完整支持中文和生僻字,避免后续插入特殊字符时出现"Incorrect string value"报错。DECIMAL(10,2)表示最高 8 位整数加 2 位小数,金额字段必须用定点数而不是FLOAT或DOUBLE,否则计算累计租金时会出现 0.1 + 0.2 不等于 0.3 的精度问题。
执行脚本后检查三个点:第一,.sql文件是否用 UTF-8 编码保存,用记事本打开另存为时容易变成 ANSI,导致中文注释乱码;第二,是否有USE house_rental;语句,很多初学者把建表语句直接粘贴到默认库执行,表建到了mysql库里;第三,测试数据是否覆盖了多种状态,比如房屋表里最好同时有空置和已租的记录,这样登录后界面不会一片空白。
3. 核心功能模块拆解:登录、签合同、收租是怎么串起来的
表结构只是骨架,真正让系统活起来的是业务代码。这部分我们按登录、签约、收租三个关键动作拆开看,每个动作都对应一组具体的类和方法。源码的view包和dao包之间通常还有一个service层,别跳过它直接写 SQL——分层清晰是这类工程能不能改成自己系统的分水岭。
3.1 登录验证:PreparedStatement 查询与界面跳转
登录功能是所有管理系统的入口,逻辑本身不复杂:界面获取用户名密码,调用 DAO 层执行查询,返回结果后决定是否跳转到主界面。但实现细节上有两个点必须讲究:SQL 注入防护和密码存储。
public User login(String username, String password) { // 参数校验放在最前面,空值直接返回,避免SQL拼接 if (username == null || password == null || username.trim().isEmpty()) { return null; } String sql = "SELECT id, username, real_name, role FROM t_user " + "WHERE username = ? AND password = ?"; // 使用PreparedStatement的占位符传参,而不是字符串拼接 // 字符串拼接SQL是注入漏洞的根源,这个习惯要从第一个项目养成 try (PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); // 常见做法是存MD5值,登录时对输入做MD5后再比对 ps.setString(2, MD5Util.md5(password)); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getInt("id")); user.setUsername(rs.getString("username")); user.setRealName(rs.getString("real_name")); user.setRole(rs.getInt("role")); return user; } } } catch (SQLException e) { e.printStackTrace(); } return null; }这段代码的关键在PreparedStatement。?占位符会把传入值当作数据而不是 SQL 语句的一部分,即使密码里包含' OR '1'='1这类内容也只会被当作普通字符串处理。关于密码,资源里如果直接用明文比对,你可以自行升级为 MD5 或加盐哈希,改动的范围很小,只涉及登录方法和初始化 SQL 里的密码字段。
登录成功后的界面跳转通常在LoginFrame的按钮事件里完成。拿到非空的User对象后,new MainFrame(user).setVisible(true),同时dispose()当前登录窗口。这里有一个细节:主框架需要当前登录人的role字段,用来控制菜单项的可用性,比如只有管理员才能看到数据初始化或用户管理按钮。
3.2 房源状态流转:合同签订时的"事务边界"
签合同是租赁系统里最容易出数据不一致的操作。它同时涉及两张表:合同表要插入一条新记录,房屋表要把状态从"空置"改成"已租"。如果先插合同再改房屋时程序崩溃,就会出现合同存在但房屋仍显示空置的脏数据。
public void signContract(Contract contract) throws SQLException { String insertContract = "INSERT INTO t_contract " + "(contract_no, house_id, tenant_id, start_date, end_date, rent_price, deposit) " + "VALUES (?, ?, ?, ?, ?, ?, ?)"; String updateHouse = "UPDATE t_house SET status = 1 WHERE id = ?"; // 关闭自动提交,手动控制事务边界 conn.setAutoCommit(false); try { try (PreparedStatement ps1 = conn.prepareStatement(insertContract)) { // 合同编号使用自定义工具类生成,规则如HT-2025-001 ps1.setString(1, contract.getContractNo()); ps1.setInt(2, contract.getHouseId()); ps1.setInt(3, contract.getTenantId()); ps1.setDate(4, contract.getStartDate()); ps1.setDate(5, contract.getEndDate()); ps1.setBigDecimal(6, contract.getRentPrice()); ps1.setBigDecimal(7, contract.getDeposit()); ps1.executeUpdate(); } try (PreparedStatement ps2 = conn.prepareStatement(updateHouse)) { ps2.setInt(1, contract.getHouseId()); ps2.executeUpdate(); } // 全部成功才提交 conn.commit(); } catch (SQLException e) { // 任意一步失败都回滚,避免合同和房屋状态不一致 conn.rollback(); throw e; } finally { conn.setAutoCommit(true); } }这段代码的核心是事务的 ACID 属性在 JDBC 层面的体现。setAutoCommit(false)之后,两条 SQL 处于同一个事务内,只有都执行成功才会commit(),否则rollback()撤销全部改动。这也是面试官常问的"JDBC 如何控制事务"的标准答案。
配套的操作是退租。退租时状态流转方向相反,需要把房屋状态改回0-空置,同时把合同状态改成2-已到期,还要计算押金退还金额。资源里如果只实现了签约没实现退租,你可以按同样的模式在ContractService里补一个terminateContract方法。状态流转的全部边界情况整理如下:
| 操作 | 合同表变化 | 房屋表变化 | 事务必要性 |
|---|---|---|---|
| 新签合同 | 插入 status=1 记录 | status 改为 1 | 必须 |
| 到期退租 | status 改为 2 | status 改为 0 | 必须 |
| 提前解约 | status 改为 3 | status 改为 0 | 必须 |
| 预订房源 | 不操作合同 | status 改为 2 | 可选 |
3.3 收租与到期:账单表怎么支撑业务统计
收租是租赁系统里最频繁的日常操作。账单表按"合同 + 账期"记录每一笔应收,比如合同编号HT-2025-001的账期2025-04金额 2800 元。收租时做的事情很朴素:把账单的status从待收改成已收,并填写实收日期和支付方式。
这里有一个常见的需求争议:账单要不要提前生成?两种方案都有项目在用。按月提前生成的好处是"待收账单列表"查询简单,坏处是要跑定时任务或手动触发生成;按需创建的好处是省事,坏处是统计逾期时要用「合同起止日期 + 月租金」现场计算。课程设计级别的资源通常选按需创建,我在自己改项目时也更倾向按需创建,减少一张流水表的状态维护成本。
账单表对统计功能的支撑体现在一个典型 SQL 上:查询某个月份的实收总金额,SELECT SUM(amount) FROM t_bill WHERE period = '2025-04' AND status = 1。如果你后续想加"本月应收""逾期未收"两个指标,也是在这个表上做条件聚合。这部分逻辑在BillService里,配合JTable展示在收租面板中。
4. 避坑与常见问题排查:5 个最容易翻车的细节
这个项目的坑集中分布在运行环境和数据两个层面。我把个人调试过程中遇到的高频问题按现象、原因、解决三段式整理出来,对照排查能省下不少时间。
4.1 连接 MySQL 失败:驱动版本和连接串是重灾区
现象:程序启动后立刻报java.sql.SQLException: Access denied for user 'root'@'localhost',或者ClassNotFoundException: com.mysql.cj.jdbc.Driver。
原因分两类。第一类是账号密码确实不对,MySQL 安装时设置的 root 密码被遗忘;第二类是驱动与 MySQL 版本不匹配,MySQL 5.x 使用com.mysql.jdbc.Driver, MySQL 8.x 使用com.mysql.cj.jdbc.Driver,连接串差异也很大。
解决方式:先确认你本地 MySQL 的版本,SELECT VERSION()。如果是 8.x,驱动 JAR 用mysql-connector-java-8.0.33.jar这类 8.x 版本,连接串写成:
private static final String URL = "jdbc:mysql://localhost:3306/house_rental" + "?useUnicode=true&characterEncoding=utf8" + "&serverTimezone=Asia/Shanghai";serverTimezone=Asia/Shanghai是 MySQL 8.x 必须的,5.x 也要加,否则报The server time zone value '??? 乱码' is unrecognized。如果确认密码没错但依然拒绝访问,去 MySQL 命令行执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';,兼容 8.x 下旧驱动的认证方式。
4.2 Windows 上 MySQL 安装后登不进去
现象:MySQL 安装过程没问题,但用命令行mysql -u root -p输入安装时设的密码一直报错,或者安装时根本就没设置密码环节。
原因:MySQL 8.0 的 Windows 安装包在 Installer 界面里设置密码的页面有"默认不填密码直接下一步"的选项,很多人跳过了这一步,导致 root 密码为空或未知。另一种情况是服务启动后与 Navicat 连接时的 host 不匹配。
解决方式:以管理员身份打开命令行,停掉 MySQL 服务后,用mysqld --skip-grant-tables模式启动服务,然后直接在命令行UPDATE mysql.user SET authentication_string=...重置密码。这个操作课程设计里一定会遇到,建议在文档里单独写一节。日常开发我一般建议装 MySQL 时记录好 root 密码,并在系统 Excel 或笔记里留存,这个习惯能省掉很多基础调试时间。
4.3 中文乱码:连接串与 IDE 编码双重作用
现象:界面按钮显示中文正常,但JTable里从数据库查出来的中文全是???,或者反过来——数据库存的中文没问题,Swing 界面输入的中文存进去就是乱码。
原因:JDBC 连接串没有指定字符集,客户端和服务器端编码不一致。?characterEncoding=utf8这个参数缺失时,连接使用 MySQL 服务端的默认字符集,Windows 下通常是 GBK,Java 侧是 UTF-8,两边一对就乱。
解决方式:连接串必须带上useUnicode=true&characterEncoding=utf8。同时检查 IDE 和源码文件的编码设置,IDEA 里统一设置File Encoding为 UTF-8,.java文件用UTF-8重新编码一次。数据库表已经建好的情况下,修改表字符集的语句是ALTER TABLE t_house CONVERT TO CHARACTER SET utf8mb4;。
4.4 日期与金额类型:字段类型选错的连锁反应
现象:合同起止日期传参时报Cannot convert value '2025-04-05' from String;金额计算出现 19.999999 这类结果;或者日期在界面显示成2025-04-05 08:00:00.0。
原因:日期字段在实体类里用了String,在数据库里用了DATETIME,类型转换全靠 JDBC 驱动兜底,性能差而且格式不可控;金额用了double或FLOAT,二进制浮点无法精确表示十进制小数。
解决方式:实体类的日期字段统一用java.util.Date,DAO 层用java.sql.Date做转换,数据库用DATE类型。金额一律使用BigDecimal,数据库用DECIMAL(10,2)。Swing 的JTable渲染金额时,getCellValue返回的BigDecimal直接toString()会显示纯数字,建议加一个TableModel层的格式化方法,保留两位小数显示。
4.5 Swing 表格不刷新:TableModel 与事件线程
现象:在主界面新增了一条房源,切到列表页,JTable里看不到;关闭窗口重新打开才出现。有时候点击按钮后界面卡死,几秒后才恢复。
原因:JTable的数据来自TableModel,你不通知模型数据变化,界面是不会主动重新查库的;卡死问题一般是查询操作跑在了 Swing 的事件分发线程(EDT)上,数据库查询耗时就阻塞了界面。
解决方式:新增数据后,重新执行查询并把结果集放入DefaultTableModel,调用model.setDataVector(data, columnNames)或fireTableDataChanged()通知界面刷新。耗时超过 200 毫秒的操作,放到SwingUtilities.invokeLater或新线程里执行,防止界面假死。这是一个很容易被忽略的 Swing 特性,我在第一次做这类项目时在这个坑上浪费了一整天。
5. 验证与进阶:六步跑通闭环,再加一个"合同到期提醒"
拿到资源后,我建议你按下面六步做一次完整验证。这个过程能同时确认源码、数据库、文档三者是否对齐,也能让你在答辩或面试时清楚说出系统的完整业务流程。
第一步,执行house_rental.sql,确认五张表全部创建成功;第二步,修改DBUtil.java里的USER和PASSWORD为本地 MySQL 账号;第三步,运行MainFrame或LoginFrame,用初始化脚本里的测试账号登录;第四步,新增一套房源,录入地址和月租金;第五步,新增一位租客,录入姓名和电话;第六步,为这套房源和租客签订合同,回到房源列表中确认状态变为"已租"。如果你能不加修改地走完这六步,说明整体环境完全没问题。
走完基础闭环后,可以尝试一个很有价值的扩展:合同到期提醒。改动点只有三处,正好能训练你理解"界面层 / 业务层 / 数据层"的分工。数据层新增一条 SQL:
SELECT c.contract_no, h.address, t.name AS tenant_name, c.end_date FROM t_contract c JOIN t_house h ON c.house_id = h.id JOIN t_tenant t ON c.tenant_id = t.id WHERE c.status = 1 AND c.end_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 30 DAY) ORDER BY c.end_date;业务层在ContractService里加一个对应方法,返回List<Contract>;界面层在主面板加一个"到期提醒"按钮,点击后弹窗展示结果。通过这个过程,你能真实地感受到在分层结构里加功能是平推而不是打乱战。
我在自己的出租房管理工具里就沿用了这套逻辑,只是把JTable换成了定时弹窗。从那以后我每次定位乱码问题,都强制先检查连接串,再检查数据库字符集,最后才去动实体类——这个排查顺序帮我省了无数冤枉时间。希望这本书里拆过的每一个坑,都能让你少走一步弯路,拿到源码后跑得比预期更顺。
本文还有配套的精品资源,点击获取