简介:基于Java与SQL Server开发完成的房屋中介公司管理系统,属于完整课程设计项目源码包,适合正在学习Java桌面应用开发、数据库编程或准备课程设计答辩的学生使用。系统运行在Windows10与JDK1.8环境中,采用Eclipse作为开发工具,通过JDBC操作SQL Server数据库,界面部分使用Swing组件搭建,业务上涵盖房源信息管理、订单查询统计、客户反馈以及基础数据设置等模块。压缩包内有88个文件,包括43个Java源代码文件、19张界面截图PNG、9个Eclipse项目配置XML、5个依赖JAR包等内容,整体体积仅3.22MB,代码结构划分了src源码目录、jdbc数据库访问目录以及img图片资源目录,便于导入项目后直接运行和学习。目前已有156人学习浏览,下载后可以得到完整的源码、项目配置文件与运行所需的第三方库,能够帮助理解Java连接SQL Server的完整流程、Swing窗体界面设计方法以及小型管理系统的分层组织思路,是一份可运行、可扩展的课程设计参考资料。
1. 基于 Java + SQL Server 的房屋中介管理系统:课设源码拆解与复现
一套 Java + SQL Server 的房屋中介公司管理系统,放在硬盘里跑不起来的人远比跑起来的多。这不是夸张,而是我在帮人看这套课设源码时的真实感受——乱掉的 JDBC 驱动、没配的 TCP/IP 协议、写错的排序规则,任何一个都能让项目卡在启动第一屏。这套系统的正身是一个标准的中介业务闭环:房源管理、客户管理、合同登记、租售跟进,界面用 Swing 搭建,数据层走 JDBC 操作 SQL Server。它适合两类人:一类是拿它当 Java 课设交作业的在校生,另一类是想快速看一遍“Swing + JDBC + SQL Server”三者怎么配合的从业者。这篇文章把你从“这是什么”带到“怎么改、坑在哪”,照着做,半小时内能把系统跑起来。
2. 系统设计与数据库建模:先看表结构再动手
2.1 模块划分与 ER 关系
这套房屋中介管理系统的功能边界很清楚,打开源码包之后你会看到,整个项目可以拆成六个业务模块:房源信息管理、客户信息管理、租赁合同管理、销售合同管理、员工登录与管理、数据统计与报表。这六个模块不是各干各的,它们的核心关系是先有房源和客户,再生成合同,最后由合同产生业绩提成。
表设计上,最关键的几张表是:HouseInfo(房源表)、CustomerInfo(客户表)、ContractInfo(合同表)、EmployeeInfo(员工表)。其中房源表和客户表是主数据的来源,合同表同时引用了房源编号、客户编号和业务员编号,形成了一条完整的中介业务链。如果你拿到源码后想改功能,第一步不是打开 Java 代码,而是先打开数据库脚本看清楚这几张表的主外键关系。
我在拆这套系统时注意到一个值得说的地方:它把租赁合同和销售合同放在同一张合同表里,通过合同类型字段区分。这种设计在小规模课设里是合理的,减少了表数量,也让统计查询少做一次 join。真实商业系统大概率会把两者拆开,因为租赁有押金、周期、续约逻辑,销售有产权过户、贷款节点,字段差异太大。但对于这个项目的定位来说,合表设计更贴合课程设计的体量。
2.2 建库脚本与表结构参数
源码包里带了建库脚本,文件名为house_manage_db.sql。打开之后你会发现,它不只是一个建表脚本,还包含初始数据。这里建议你先别急着在 SSMS 里直接执行,先看一眼脚本里用了哪些数据类型和约束。
-- 房源信息表 CREATE TABLE HouseInfo ( houseId INT IDENTITY(1,1) PRIMARY KEY, -- 房源编号,自增主键 houseNo VARCHAR(20) NOT NULL, -- 房源编号(业务编号,区别于主键) houseType VARCHAR(10) NOT NULL, -- 户型,如 2室1厅 area DECIMAL(8,2) NOT NULL, -- 建筑面积,单位平方米 floorNum INT, -- 所在楼层 totalFloor INT, -- 总楼层 price DECIMAL(12,2) NOT NULL, -- 租金或售价 isSell BIT DEFAULT 0, -- 0=出租,1=出售 status VARCHAR(10) DEFAULT '未成交', -- 房源状态 address NVARCHAR(100) NOT NULL -- 地址,用NVARCHAR存中文 );houseId用IDENTITY(1,1)做主键,这意味着插入数据时不需要显式传值,SQL Server 会自动生成。这个设计在后续 JDBC 操作里会牵扯到一个关键点:怎样在插入后拿到自增的主键值。我在后面第 5 章的避坑里会专门讲,这里先记住用Statement.RETURN_GENERATED_KEYS这个方向。
price用DECIMAL(12,2),既支持大额价格,又避免FLOAT带来的精度误差。很多人做课设喜欢用FLOAT,在房产这种以万为单位计算的场景下,浮点误差会在统计报表里被放大,所以这套源码的类型选择是规范的。
2.3 初始数据与登录账号
脚本末尾有一段INSERT INTO EmployeeInfo的语句,默认管理员账号是admin,密码是admin123。客户表里也插了两条测试数据,房 source 表插了三套房源。这些初始数据是为了让系统跑起来后界面不至于空空荡荡。
这里有个容易忽略的点:EmployeeInfo表的密码列是VARCHAR(32),存的是 MD5 加密后的值。登录时 Java 端先用MD5工具类把输入密码转成摘要,再和数据库里的值比较。如果你把脚本里的初始密码直接改成明文,登录逻辑就会失效。我在复现时见过有人把数据库里的密码改成123456,然后 Java 端一直登录失败,原因就是登录代码里强制走了 MD5。
进 SSMS 执行脚本时,注意选中正确的数据库实例和库名,默认库名是HouseManageDB。如果 SSMS 报“对象名无效”,多半是脚本开头有USE [HouseManageDB]但你的库名不一致,改一下即可。
3. 打通 JDBC:连接配置与驱动选择
3.1 驱动版本与连接串参数
这个项目用的是 SQL Server 2008 以上的兼容写法,驱动类名是com.microsoft.sqlserver.jdbc.SQLServerDriver。这个类名在 sqljdbc4.jar 和更高版本的 mssql-jdbc 驱动里都存在,区别在于旧驱动支持的 JDK 版本比较老。如果你本机是 JDK 8,建议直接用 mssql-jdbc 6.2 以上的驱动,兼容性更好。
连接串的核心格式如下:
String url = "jdbc:sqlserver://localhost:1433;databaseName=HouseManageDB;encrypt=true;trustServerCertificate=true"; String user = "sa"; String password = "123456";参数说明:localhost:1433是 SQL Server 默认端口,如果你安装时改过端口,务必同步修改;databaseName是库名,必须和建库脚本里一致;encrypt=true和trustServerCertificate=true是应对新版驱动默认加密行为的关键,如果缺失,JDK 8 下可能报 SSL 握手失败。
3.2 连接工具类与 DAO 层写法
源码里有一个DBUtil类,作用是统一管理连接的创建与关闭。我拆过很多课设,这个类的写法大同小异,核心是把驱动加载放静态块里,用DriverManager.getConnection()获取连接,关闭资源时遵循“后开先关”的顺序。
public class DBUtil { private static final String DRIVER = "com.microsoft.sqlserver.jdbc.SQLServerDriver"; private static final String URL = "jdbc:sqlserver://localhost:1433;databaseName=HouseManageDB;encrypt=true;trustServerCertificate=true"; private static final String USER = "sa"; private static final String PASSWORD = "123456"; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } public static void close(ResultSet rs, PreparedStatement ps, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (ps != null) { try { ps.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }逻辑说明:这段代码先把驱动注册动作封装到静态块里,保证类加载时驱动只注册一次,避免每条连接重复加载;关闭资源的三段式写法虽然笨,但保证在连接用完后的释放顺序是 ResultSet → PreparedStatement → Connection,防止连接泄漏。
在实际运行时,Class.forName(DRIVER)在 JDBC 4.0 之后已经不是必须的,驱动 jar 包含META-INF/services会自动注册。保留这行代码能让代码兼容性更好,也让初学者更容易理解驱动加载的过程。如果你的项目用了连接池,这个类可以不用,直接交给池管理即可。
3.3 登录校验与防止 SQL 注入
登录模块是这套系统里少数需要安全意识的代码。我拆源码时发现它用了PreparedStatement而不是Statement,这个细节值得说。使用PreparedStatement的setString传参,SQL 会预编译后再填充参数,注入语句只被当成普通字符串处理,从根本上杜绝了'or'1'='1这类密码绕过问题。
public boolean checkLogin(String username, String md5Password) { String sql = "SELECT COUNT(*) FROM EmployeeInfo WHERE empNo = ? AND password = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, username); ps.setString(2, md5Password); try (ResultSet rs = ps.executeQuery()) { if (rs.next()) { return rs.getInt(1) > 0; } } } catch (SQLException e) { e.printStackTrace(); } return false; }这里的try-with-resources写法是 Java 7 以后的推荐风格,资源自动关闭,不用手写close()。参数说明:第一个?对应员工编号,不是员工姓名;第二个?必须传 MD5 后的密码。如果你改了登录逻辑,改成明文比对,一定要同时调整这条 SQL,否则两边对不上。
4. 核心业务实现:从房源登记录入到合同生成
4.1 房源模块:CRUD 与组合条件查询
房源管理是本系统的核心模块,也是一个 Java 课设必须做完整的部分。源码里HouseDao承担了房源表的增删改查,其中最有参考价值的是它的组合查询——支持按户型、区域、价格区间三个条件同时过滤。
public List<House> searchHouses(String houseType, String area, double minPrice, double maxPrice) { StringBuilder sql = new StringBuilder("SELECT * FROM HouseInfo WHERE 1=1"); if (houseType != null && !houseType.isEmpty()) { sql.append(" AND houseType = ?"); } if (area != null && !area.isEmpty()) { sql.append(" AND address LIKE ?"); } if (minPrice > 0) { sql.append(" AND price >= ?"); } if (maxPrice > 0) { sql.append(" AND price <= ?"); } // 执行查询,依次设置参数 }逻辑说明:这里的拼 SQL 技巧是典型的“动态查询模板”——先写WHERE 1=1,后面每个条件都用AND开头。这样任何一个条件缺失都不会破坏语法结构,省去判断“是否第一个条件”的麻烦。参数设置顺序必须和拼接顺序一致,先户型、再区域、后价格区间,错一个就查不出数据。
在 UI 层,这套系统的搜索事件通过addActionListener绑定到查询按钮,用户选完条件后点击查询,结果刷新到JTable里。注意address LIKE ?的参数值需要在 Java 端手动拼%和_,SQL Server 的 LIKE 和 MySQL 一样支持通配符。
4.2 客户模块:意向状态与跟进记录
客户模块对应CustomerDao,它比房源模块多了一层业务逻辑:客户意向状态管理。状态字段是VARCHAR(10),值域包括“未看房”“已看房”“已成交”“已流失”。在 DAO 层,状态流转的控制不是简单 UPDATE,而是带条件的位置比较:
public boolean updateCustomerStatus(int customerId, String oldStatus, String newStatus) { String sql = "UPDATE CustomerInfo SET status = ? WHERE customerId = ? AND status = ?"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, newStatus); ps.setInt(2, customerId); ps.setString(3, oldStatus); return ps.executeUpdate() == 1; } catch (SQLException e) { e.printStackTrace(); return false; } }这里的核心是 WHERE 里带上了oldStatus作为乐观锁条件。它的意义在于:假如两个窗口同时处理同一个客户,一个把状态从“已看房”改成“已成交”,另一个还想从“已看房”改成“已流失”,第二个 UPDATE 的执行结果是 0,因为它查询到的旧状态已经不存在。这种写法在课设里算超纲,但能很好地解释什么叫“并发安全”。
执行结果的判定用executeUpdate() == 1而不是> 0,原因是更新一条记录只可能返回 0 或 1。如果返回 1,说明状态确实发生迁移;返回 0,说明旧状态不匹配,前端就要给出提示。
4.3 合同模块:事务控制与自增主键获取
合同生成是整套系统业务链的终点。一个合同对象包含房源编号、客户编号、员工编号、合同类型(租赁/销售)、成交金额、签订日期等字段。这里的代码最能看出作者功力的是插入操作,因为它在单条INSERT里同时完成了两件事:写入合同记录并返回自增主键,同时更新房源状态为“已成交”。
public int addContract(Contract contract) throws SQLException { String sql = "INSERT INTO ContractInfo (houseId, customerId, empId, contractType, dealPrice, signDate) VALUES (?, ?, ?, ?, ?, ?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setInt(1, contract.getHouseId()); ps.setInt(2, contract.getCustomerId()); ps.setInt(3, contract.getEmpId()); ps.setString(4, contract.getContractType()); ps.setBigDecimal(5, contract.getDealPrice()); ps.setDate(6, new java.sql.Date(contract.getSignDate().getTime())); ps.executeUpdate(); try (ResultSet keys = ps.getGeneratedKeys()) { if (keys.next()) { return keys.getInt(1); } } } return 0; }关键参数是conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)。默认的prepareStatement(sql)执行后不返回数据库生成的主键,必须显式声明这个标志,然后通过getGeneratedKeys()拿到结果集。我见过不少人写完 INSERT 后再用SELECT MAX(contractId)去查主键,这在单用户课设里能跑通,但并发场景下可能拿到别人的主键值。
合同插入后,房源状态更新不能用自动提交模式,必须和合同插入放在同一个事务里。源码里这部分在 Service 层控制,核心三步:开启事务 → 插入合同 → 更新房源状态 → 提交;任一步失败则回滚。代价是这个业务逻辑的完整代码较长,但它是整套系统里最值得模仿的一段。
4.4 数据报表:SQL Server 聚合查询与 Java 端展示
统计报表模块用的是 SQL Server 的聚合函数GROUP BY。源码里做了一个“按合同类型统计成交额”的报表,在ReportDao里对应一条命令:
SELECT contractType, COUNT(*) AS cnt, SUM(dealPrice) AS totalAmount FROM ContractInfo GROUP BY contractType这段 SQL 在 SSMS 里执行没任何问题,但放到 Java 端要注意结果集的取数类型。SUM(dealPrice)返回的类型在 JDBC 里可以映射为BigDecimal,如果强转成double或float,金额精度会立刻丢失。源码里用的是getBigDecimal("totalAmount"),然后在表格模型里用setValueAt显示为字符串。这个细节让报表的数字和 SQL Server 里查询结果完全一致,不出现小尾巴。
如果你需要导出 Excel,这套源码里没有整合 POI,但可以在报表结果集上自行封装。常见做法是先把数据填到Vector或二维数组,再通过JFileChooser弹窗选保存路径,最后逐行写入 CSV 文件。CSV 可以用 Excel 直接打开,做到这一步,课设答辩时加分明显。
5. 避坑记录:SQL Server 版 Java 项目的八个典型问题
5.1 驱动类名报 ClassNotFoundException
现象:运行项目时控制台抛出ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver。
原因:项目里没有引入驱动 jar,或引用的 jar 位置不对。很多人下载了源码包却忘了看lib目录是否完整,直接把源码导入 IDE 就跑。
解决:检查项目lib文件夹下是否有sqljdbc4.jar或mssql-jdbc-x.x.jar;如果没有,手动下载对应版本并添加到 Build Path。注意旧驱动不支持 JDK 9+,JDK 11 环境下用 mssql-jdbc 8.2 以上版本更稳。
5.2 端口连不上:SQL Server TCP/IP 协议未启用
现象:报Cannot connect to localhost:1433,但 SSMS 能正常连接。
原因:SQL Server 默认安装后 TCP/IP 协议是禁用的,JDBC 走不了 1433 端口;SSMS 能连是因为它走的是 Shared Memory 或 Named Pipes。
解决:打开“SQL Server 配置管理器”,展开“SQL Server 网络配置”,选中实例名,右侧把 TCP/IP 启用;然后重启 SQL Server 服务。如果这时还连不上,再检查 Windows 防火墙是否放行 1433。这个优先级最高的数据库连接翻车点。
5.3 中文乱码或中文查询不出来
现象:界面上房源地址显示乱码,或者按中文关键字搜索无结果。
原因:数据库表字段用了VARCHAR来存中文,而 Java 端字符串是 UTF-16 编码,两边转换时发生编码不一致。另外 SQL Server 的默认排序规则是Chinese_PRC_CI_AS,如果建表时用了非中文字段类型,存储中文就会出现问号或方框。
解决:建表时地址等中文字段用NVARCHAR而不是VARCHAR;连接串里加characterEncoding=UTF-8(虽然 JDBC 驱动不太吃这个参数,但规范上写着没坏处);如果是已经建好的表,用ALTER TABLE HouseInfo ALTER COLUMN address NVARCHAR(100)调整类型。
5.4 密码带特殊字符导致连接失败
现象:数据库连接串里的password=123@abc能测试连通,但 Java 程序连接时报连接属性异常。
原因:@等特殊字符在 JDBC URL 里需要转义,直接裸写会被解析成连接串的一部分,导致认证信息错位。
解决:用DriverManager连接时,把用户名和密码作为参数传入getConnection(String url, String user, String password),而不是拼在 URL 字符串里。这个写法本身就是最佳实践,不用做转义。
5.5 SQL Server 安装失败:无法找到数据库引擎启动句柄
现象:安装 SQL Server 2016/2019 时,安装界面报“无法找到数据库引擎启动句柄”,安装进程直接回滚。
原因:这个报错通常与系统已安装过 SQL Server 但服务残留有关,或者系统账户权限不足,导致数据库引擎服务无法启动。
解决:先用管理员身份运行命令提示符,执行net start查看是否有残留的MSSQLSERVER服务;如果有,用net stop停掉后删除对应注册表服务项;再重新安装。安装时建议在“服务器配置”页指定NT AUTHORITY\SYSTEM账户启动服务,避开权限问题。
5.6 日期字段转换报错
现象:合同表的signDate字段在 Java 端用Date类型取值时抛ClassCastException。
原因:JDBC 从 SQL Server 的datetime类型取出的对象是java.sql.Timestamp,它和java.util.Date不是继承关系,直接强转会失败。
解决:统一用java.sql.Date接值,或者用Timestamp取值后手动转java.util.Date。插入时也注意方向,用setDate传参时先转java.sql.Date。这套源码里已经做对,但如果你自己写新模块很容易踩。
5.7 表和字段名撞上 SQL Server 保留字
现象:执行建表脚本时,某条 CREATE 语句报语法错误或“xxx 附近有语法错误”。
原因:表名或字段名和 SQL Server 保留字冲突,比如identity、order、number这类单词。
解决:改表结构时避开保留字;确实要用,则给表名和字段名加方括号[]。检查源码里的 SQL 语句,凡是加[]的字段名,千万别去掉,去掉就是语法错误。
5.8 JTable 刷新不显示新数据
现象:新增一条房源后,列表界面没有立即刷新,必须重启程序才看到。
原因:DAO 插入数据后没有重新执行查询,JTable的DefaultTableModel还是旧的数据集。
解决:在新增成功后的回调里,重新调用searchHouses并setModel刷新表格。注意要把清空旧行的setRowCount(0)放在填充新数据之前,否则旧数据会在表格里叠加显示。
6. 验证与扩展:把课设做成经得起答辩的完整作品
一个课设源码,如果你只是让它跑起来,那它和班里同学的作品没有本质区别。拉高完成度的方法是建立自己的验证体系,把每个模块的输入、执行、输出都过一遍,并记录下与默认行为的差异。我的习惯是每次拿到这类源码,先走三组验证:登录组、业务流转组、异常组。
登录组验证的内容是三个账号的行为差异,admin能看到全部菜单,普通员工只能看到房源和客户,验证完成后要确认数据库里的 MD5 密码不能被替换成明文。业务流转组则模拟一条完整链路:录入房源 → 新增客户 → 客户登记看房意向 → 生成合同 → 房源状态自动变为“已成交”——这五步在界面上全部走通,回到 SSMS 查合同表确认外键字段和金额正确。异常组更直接:输入不存在的员工编号登录、提交价格为零的合同、删除被合同引用的房源,系统是否给出友好错误提示而不是抛堆栈异常。
扩展方向建议做一个二选一:给客户模块加一个跟进记录表FollowRecord,或者给报表模块加一个整体导出 CS V 的功能。前者涉及表设计变更,后者更轻量,约三十行代码。如果你选导出 CSV,核心逻辑是先查出报表结果集,再用BufferedWriter按行写入,表头用英文或中文都可:
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get("report.csv"))) { writer.write("合同类型,合同数量,成交总额"); writer.newLine(); while (rs.next()) { writer.write(rs.getString("contractType") + "," + rs.getInt("cnt") + "," + rs.getBigDecimal("totalAmount")); writer.newLine(); } }注意金额字段用BigDecimal写入,避免浮点尾巴。导出成功后检查文件大小和首行内容,确保打开是规整的表格而不是乱码。
这套系统最值得带走的东西不是它的代码量,而是“Java + SQL Server 如何在一个课设尺度上合作”的完整示范。从那以后我每次拿到课设源码,都强制走一遍“看脚本 → 建库 → 配驱动 → 验证登录 → 走核心业务”这条路,不去猜测哪里可能出错,而是直接让错误暴露出来。希望帮到你。
本文还有配套的精品资源,点击获取