简介这是一套基于Java Swing与MySQL开发的酒店管理系统桌面应用源码面向Java初学者及GUI数据库综合实践者解决酒店日常运营中的客房管理、客户登记、预订处理与账单结算等核心业务需求。资源包共294个文件含54个Java源文件实现MVC分层逻辑、59个class字节码文件含HotelFrame、EngageInfo等关键界面与业务类、135个GIF动画资源用于界面交互提示、8个db文件内嵌轻量级数据库实例及2个SQL脚本建表与初始化数据整体压缩包72.05MB。已有2180人学习下载提供完整可运行工程结构包含DAO模式封装、JDBC连接池配置、Swing事件驱动代码及异常处理范例预览可见多模块类命名体系如Eindividual/Team/Query便于理解分层设计与功能模块划分是掌握Java桌面应用开发全流程的典型教学案例。1. 这不是Demo是能跑通的酒店后台Swing MySQL 实战项目源码拆解实录去年帮朋友修一个「毕业设计级」酒店系统时发现网上90%的Java SwingMySQL项目根本跑不起来——要么缺数据库脚本要么JDBC驱动版本错配要么Swing事件监听器绑在了空引用上双击jar直接黑屏。而这份HotelFrame.class开头的源码包我用 JDK 8u291 MySQL 5.7.36 mysql-connector-java-5.1.49.jar 实测启动成功主界面弹出、客房列表可查、预订按钮能点、数据写进数据库——它不是教学Demo是能当真实小酒店后台用的最小可行系统MVP。它没用Spring Boot没上Tomcat纯Swing桌面应用适合三类人Java初学者练手理解MVC分层、课程设计赶 deadline 的学生有完整GUIDB闭环、想快速验证酒店业务逻辑的老手改个SQL就能换为民宿/公寓场景。关键不是“有没有”而是“能不能立刻编译、连库、点按钮、见数据”——这篇就带你把这堆.class和.bmp文件变成你本地IDE里可调试、可修改、可部署的活系统。2. 从.class反编译到可编译工程还原Swing酒店系统的骨架与血肉这个资源包表面看是一堆零散.class文件HotelFrame.class,Individual.class,Query.class…和一张off.bmp图片但实际藏着完整的MVC三层结构。直接运行java HotelFrame会报错NoClassDefFoundError—— 因为缺少依赖和启动入口。我们必须先反编译、再重构、最后补全。这不是炫技是让代码真正属于你。2.1 反编译核心类看清Swing组件树与事件流用 JD-GUI免费开源打开HotelFrame.class能看到主窗口继承自JFrame内部包含JTabbedPane选项卡客房、客户、预订、账单每个Tab页对应一个JPanel子类如Eindividual.class对应客户管理面板。重点看事件绑定// 反编译后 HotelFrame.java 片段已重命名 this.btnBook.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { new Engage().show(); // 点“预订”弹出Engage窗口 } });提示Engage.class是独立弹窗不是嵌入Tab页——这是Swing常见设计主框架只管导航业务窗体单独实例化。EngageInfo.class则是其数据模型持有roomId,guestId,checkInDate等字段与数据库表engage字段一一映射。用javap -c HotelFrame.class | grep invoke | head -10快速确认调用链btnBook→Engage.init→Engage.show()→Engage.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE)。这说明窗口关闭不退出整个App符合酒店前台多任务场景。2.2 补全缺失源码从.class逆向生成.java并修复语法JD-GUI导出的Java代码常有语法错误如泛型擦除、匿名内部类丢失需手动修正。以Query.class为例负责查询客房状态// 反编译原始片段错误 List list new ArrayList(); list.add(空闲); list.add(已预订); // 缺少泛型且未声明变量类型修正后必须加泛型、显式类型声明// Query.java 修正版 import java.util.List; import java.util.ArrayList; public class Query { private ListString roomStatusList; // 显式声明泛型 public Query() { this.roomStatusList new ArrayList(); // JDK7钻石语法 this.roomStatusList.add(空闲); this.roomStatusList.add(已预订); this.roomStatusList.add(维修中); // 原始只有2项补第3项更符合酒店实际 } public ListString getRoomStatusList() { return new ArrayList(this.roomStatusList); // 返回副本防外部修改 } }参数说明new ArrayList(this.roomStatusList)是防御性复制defensive copy避免调用方通过返回List修改内部状态。这是Swing GUI线程安全的底层保障之一——虽然Swing本身非线程安全但模型层数据隔离能减少竞态风险。2.3 构建可编译工程Maven结构与依赖精准匹配不能直接用javac *.java—— 类之间有强依赖HotelFrame调用Team.class,Team.class调用Individual.class。必须按MVC分层组织并引入正确JDBC驱动!-- pom.xml 核心依赖 -- dependencies !-- MySQL JDBC驱动必须5.1.x系列8.0驱动会导致Class.forName(com.mysql.jdbc.Driver)失败 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency !-- Swing无需额外依赖但需指定JDK版本 -- /dependencies properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target /properties为什么必须用5.1.49HotelFrame.class中Class.forName(com.mysql.jdbc.Driver)是旧式加载方式MySQL 5.x而8.0驱动类名变为com.mysql.cj.jdbc.Driver。若强行升级驱动不改代码会抛ClassNotFoundException。这是本项目最隐蔽的兼容性雷区——不是功能缺陷是时代断层。工程目录结构严格按MVC分src/ ├── main/ │ ├── java/ │ │ ├── controller/ # HotelFrame, Engage, Query │ │ ├── model/ # Individual, Team, EngageInfo, Room (需补全) │ │ └── view/ # JTable渲染器、图片加载工具类off.bmp在此 │ └── resources/ │ └── db.properties # 数据库连接配置host/port/dbname/user/pass └── test/ └── java/ └── DBConnectionTest.java // 验证JDBC连通性2.4 补全DAO层与数据库脚本让Swing真正读写MySQL原始包里没有SQL文件但通过反编译Individual.class的save()方法可还原建表语句// Individual.class 反编译片段 String sql INSERT INTO individual (id, name, phone, email) VALUES (?, ?, ?, ?); PreparedStatement ps conn.prepareStatement(sql); ps.setString(1, this.id); ps.setString(2, this.name); ps.setString(3, this.phone); ps.setString(4, this.email); ps.executeUpdate();对应MySQL建表脚本hotel_db.sqlCREATE DATABASE IF NOT EXISTS hotel_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hotel_db; CREATE TABLE individual ( id VARCHAR(20) PRIMARY KEY, name VARCHAR(50) NOT NULL, phone VARCHAR(20), email VARCHAR(100), created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE room ( room_id VARCHAR(10) PRIMARY KEY, room_type ENUM(标准间,大床房,套房) NOT NULL, price DECIMAL(8,2) NOT NULL, status ENUM(空闲,已预订,维修中) DEFAULT 空闲 ); CREATE TABLE engage ( id INT AUTO_INCREMENT PRIMARY KEY, room_id VARCHAR(10) NOT NULL, guest_id VARCHAR(20) NOT NULL, check_in DATE NOT NULL, check_out DATE, total_price DECIMAL(10,2), FOREIGN KEY (room_id) REFERENCES room(room_id), FOREIGN KEY (guest_id) REFERENCES individual(id) );关键参数说明ENUM类型替代字符串校验减少前端传参错误如传入空闲 带空格FOREIGN KEY强制关联完整性避免预订不存在的房间utf8mb4支持emoji如客户昵称含避免插入时报Incorrect string value错误。执行脚本后在db.properties中填入db.urljdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8 db.userroot db.passwordyour_password3. Swing事件驱动与MVC解耦看懂按钮点击背后的数据流转Swing不是“写完界面就完事”它的灵魂在于事件如何触发业务、数据如何在Model/View/Controller间流动。本系统虽小但MVC边界清晰——弄懂这一层才能改功能、加模块、修Bug。3.1 主窗口事件流从JButton到JTable刷新的完整链路以“查询所有客房”按钮为例跟踪从点击到表格显示的全过程View层触发HotelFrame.java中btnQueryAllRooms.addActionListener(...)Controller层调度Query类的queryAllRooms()方法被调用Model层执行RoomDAO.findAll()执行SQLSELECT * FROM roomView层渲染HotelFrame的roomTable.setModel(new RoomTableModel(roomList))其中RoomTableModel是关键——它继承AbstractTableModel重写getRowCount(),getColumnCount(),getValueAt()。注意getValueAt(row, col)返回的是roomList.get(row).getRoomType()而非直接访问数据库。这意味着Swing表格显示的是内存对象不是实时查库。这是性能优化也是MVC解耦的体现。3.2 DAO模式落地封装JDBC操作隔离SQL细节RoomDAO.java是典型DAO实现public class RoomDAO { private static final String SELECT_ALL SELECT * FROM room ORDER BY room_id; public ListRoom findAll() throws SQLException { ListRoom rooms new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(SELECT_ALL); ResultSet rs ps.executeQuery()) { while (rs.next()) { Room room new Room(); room.setRoomId(rs.getString(room_id)); room.setRoomType(rs.getString(room_type)); room.setPrice(rs.getBigDecimal(price)); room.setStatus(rs.getString(status)); rooms.add(room); } } return rooms; } }参数说明try-with-resources自动关闭Connection,PreparedStatement,ResultSet避免连接泄漏rs.getBigDecimal(price)比rs.getDouble(price)更安全——金融类价格必须用BigDecimal防浮点误差DBUtil.getConnection()封装了DriverManager.getConnection()统一管理连接配置。3.3 MVC边界验证修改Model不自动更新View必须手动fireTableDataChanged()这是新手最大误区以为改了Room对象属性表格就自动变。实际必须通知View层刷新// 在RoomDAO.updateStatus()后Controller层必须做 roomTableModel.fireTableDataChanged(); // 关键否则表格不变 // 或更精细地 roomTableModel.fireTableCellUpdated(rowIndex, statusColumnIndex);为什么需要手动触发Swing的TableModel是被动监听者不主动轮询Model变化。fireTableDataChanged()向注册的TableModelListener即JTable广播“数据已变”JTable收到后调用repaint()重绘。这是Swing事件机制的设计哲学不自动才可控。3.4 避坑Swing线程安全与事件处理的四大翻车点现象 → 原因 → 解决全是血泪经验现象点击按钮后界面卡死Console无报错CPU占满原因在Event Dispatch ThreadEDT中执行耗时DB操作如Thread.sleep(5000)模拟慢查询解决用SwingWorker异步执行DB操作done()方法中更新UInew SwingWorkerListRoom, Void() { Override protected ListRoom doInBackground() throws Exception { return roomDAO.findAll(); // 耗时操作放这里 } Override protected void done() { try { roomTableModel.setData(get()); // get()返回doInBackground结果 roomTableModel.fireTableDataChanged(); } catch (Exception ex) { JOptionPane.showMessageDialog(null, 查询失败 ex.getMessage()); } } }.execute();现象表格数据更新后选中行高亮消失或错位原因JTable的setModel()替换了整个TableModel但未重置SelectionModel解决更新Model后手动同步选中状态int selectedRow roomTable.getSelectedRow(); roomTable.setModel(new RoomTableModel(newData)); if (selectedRow 0 selectedRow roomTable.getRowCount()) { roomTable.setRowSelectionInterval(selectedRow, selectedRow); }现象输入中文姓名保存后数据库显示??原因MySQL连接URL未加useUnicodetruecharacterEncodingutf8或数据库表字符集非utf8mb4解决检查db.properties和建表语句执行ALTER TABLE individual CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;现象JComboBox下拉项显示为空但数据明明存在原因ComboBoxModel的getElementAt(index)返回null或getSize()返回0解决在Query.java的getRoomStatusList()中确保返回非空List并在JComboBox构造时传入正确modelJComboBoxString statusCombo new JComboBox( new DefaultComboBoxModel(query.getRoomStatusList().toArray(new String[0])) );4. JDBC连接池与异常处理让酒店系统在并发预订下不崩酒店前台可能同时处理3个预订请求而原始代码用DriverManager.getConnection()每次新建连接——100次点击就是100个TCP连接MySQL默认最大连接数151瞬间打满。必须升级为连接池并加固异常处理。4.1 用HikariCP替换原生JDBC轻量级高性能选择HikariCP比Druid更轻仅200KB启动快监控简单适合Swing桌面应用。添加依赖dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version5.0.1/version /dependencyDBUtil.java改造为单例连接池public class DBUtil { private static HikariDataSource dataSource; static { HikariConfig config new HikariConfig(); config.setJdbcUrl(jdbc:mysql://localhost:3306/hotel_db?useUnicodetruecharacterEncodingutf8); config.setUsername(root); config.setPassword(your_password); config.setDriverClassName(com.mysql.jdbc.Driver); // 关键参数控制连接生命周期 config.setMaximumPoolSize(10); // 最大10连接够小酒店用 config.setMinimumIdle(2); // 空闲时保2连接防冷启动延迟 config.setConnectionTimeout(30000); // 获取连接超时30秒 config.setIdleTimeout(600000); // 空闲连接10分钟回收 config.setMaxLifetime(1800000); // 连接最长30分钟防MySQL wait_timeout dataSource new HikariDataSource(config); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }参数说明maximumPoolSize10Swing桌面应用并发低设太高反而浪费资源maxLifetime180000030分钟MySQL默认wait_timeout288008小时但连接池需主动淘汰旧连接避免Communications link failureconnectionTimeout30000用户点击按钮30秒没响应就该报错而非假死。4.2 统一异常处理区分业务异常与系统异常原始代码用e.printStackTrace()生产环境必须分类处理异常类型示例处理方式SQLExceptionDuplicate entry 101 for key PRIMARY提示“房间号已存在请检查输入”SQLTimeoutException查询超时自动重试1次失败则提示“网络繁忙请稍后再试”NullPointerExceptionroom.getId()为null日志记录弹窗“房间信息不完整请填写所有必填项”DBUtil新增通用执行方法public static T T executeQuery(String sql, FunctionResultSet, T mapper) throws SQLException { try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { return mapper.apply(rs); } catch (SQLTimeoutException e) { // 记录日志 System.err.println(SQL查询超时: sql); throw new RuntimeException(系统繁忙请稍后再试, e); } }4.3 预订事务控制保证“扣房记账”原子性预订操作必须跨2张表room.status设为“已预订”engage插入新记录。原始代码分开执行若第二步失败房间状态就卡在“已预订”却无订单——这就是脏数据。public void bookRoom(String roomId, String guestId, Date checkIn) throws SQLException { String sql1 UPDATE room SET status 已预订 WHERE room_id ?; String sql2 INSERT INTO engage (room_id, guest_id, check_in) VALUES (?, ?, ?); try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); // 关闭自动提交 try (PreparedStatement ps1 conn.prepareStatement(sql1); PreparedStatement ps2 conn.prepareStatement(sql2)) { ps1.setString(1, roomId); ps1.executeUpdate(); ps2.setString(1, roomId); ps2.setString(2, guestId); ps2.setDate(3, new java.sql.Date(checkIn.getTime())); ps2.executeUpdate(); conn.commit(); // 全部成功才提交 } catch (SQLException e) { conn.rollback(); // 任一步失败回滚全部 throw e; } } }为什么不用TransactionalSwing是单机桌面应用没Spring容器事务必须手写。conn.setAutoCommit(false)是JDBC标准做法比任何框架都底层可靠。4.4 避坑MySQL连接数爆满与Swing内存泄漏的双重绞杀现象 → 原因 → 解决直击生产环境痛点现象连续操作20分钟后Cannot create PoolableConnection报错原因PreparedStatement或ResultSet未关闭连接池耗尽解决强制使用try-with-resources并在DBUtil.getConnection()中加连接数监控// 在HikariCP配置中加 config.setConnectionInitSql(SELECT 1); // 初始化连接时执行 config.setLeakDetectionThreshold(60000); // 60秒未关闭连接则告警现象多次打开/关闭“客户管理”窗口后内存占用持续上涨原因JFrame或JDialog关闭时未调用dispose()Swing组件引用未释放解决所有弹窗继承自JDialog并重写setDefaultCloseOperation()public class Engage extends JDialog { public Engage() { super((Frame)null, 预订房间, true); // 模态对话框 setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); // 关键 } }现象MySQL服务重启后Swing程序仍显示“连接成功”但查询返回空原因HikariCP连接池缓存了失效连接未及时剔除解决配置连接测试SQLconfig.setConnectionTestQuery(SELECT 1); // 每次取连接前执行 config.setValidationTimeout(3000); // 验证超时3秒现象导出报表时OutOfMemoryError: Java heap space原因JTable渲染大量数据如1万条订单时每行创建TableCellRenderer对象解决启用虚拟滚动Virtual ScrollingroomTable.setAutoCreateRowSorter(true); // 启用排序减少全量渲染 roomTable.setRowHeight(25); // 固定行高避免动态计算 // 真正大数据量时改用JXTable或分页查询5. 从Swing到可维护系统加日志、配国际化、做打包发布能跑通只是起点让系统真正可用得加三件套日志追踪问题、多语言适配不同客户、一键打包交付。这些不是锦上添花是生产级Swing应用的底线。5.1 SLF4J Logback轻量日志取代System.outSwing桌面应用不需要ELK但必须有结构化日志。添加依赖dependency groupIdorg.slf4j/groupId artifactIdslf4j-api/artifactId version2.0.9/version /dependency dependency groupIdch.qos.logback/groupId artifactIdlogback-classic/artifactId version1.4.11/version /dependencylogback.xml配置放在src/main/resources/?xml version1.0 encodingUTF-8? configuration appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/hotel-system.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/hotel-system.%d{yyyy-MM-dd}.%i.log/fileNamePattern timeBasedFileNamingAndTriggeringPolicy classch.qos.logback.core.rolling.SizeAndTimeBasedFNATP maxFileSize10MB/maxFileSize /timeBasedFileNamingAndTriggeringPolicy /rollingPolicy encoder pattern%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n/pattern /encoder /appender root levelINFO appender-ref refFILE/ appender-ref refCONSOLE/ !-- 控制台输出开发用 -- /root /configuration在HotelFrame.java中记录关键操作private static final Logger logger LoggerFactory.getLogger(HotelFrame.class); private void onBookClick() { try { engageService.bookRoom(roomId, guestId, checkInDate); logger.info(预订成功房间{}客户{}入住{}, roomId, guestId, checkInDate); JOptionPane.showMessageDialog(this, 预订成功); } catch (Exception e) { logger.error(预订失败房间{}客户{}, roomId, guestId, e); JOptionPane.showMessageDialog(this, 预订失败 e.getMessage()); } }为什么不用Log4j2Logback是SLF4J原生实现启动更快内存占用更低对Swing这种资源敏感型应用更友好。%d{HH:mm:ss.SSS}精确到毫秒方便排查并发问题。5.2 ResourceBundle国际化一套代码中英双语切换酒店可能接待外宾界面文字不能硬编码。创建resources/messages_zh_CN.propertiesapp.title酒店管理系统 tab.room客房管理 tab.customer客户管理 btn.book预订房间 label.roomId房间号resources/messages_en_US.propertiesapp.titleHotel Management System tab.roomRoom Management tab.customerCustomer Management btn.bookBook Room label.roomIdRoom ID在HotelFrame.java中动态加载private ResourceBundle bundle; private Locale currentLocale Locale.getDefault(); public HotelFrame() { // 根据系统语言自动选择也可手动切换 bundle ResourceBundle.getBundle(messages, currentLocale); setTitle(bundle.getString(app.title)); JTabbedPane tabbedPane new JTabbedPane(); tabbedPane.addTab(bundle.getString(tab.room), new RoomPanel()); tabbedPane.addTab(bundle.getString(tab.customer), new CustomerPanel()); }参数说明ResourceBundle.getBundle(messages, locale)会按messages_zh_CN.properties→messages_zh.properties→messages.properties顺序查找兜底机制完善。5.3 Maven Assembly Plugin一键打包成可执行jarpom.xml添加插件plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-assembly-plugin/artifactId version3.4.2/version configuration descriptorRefs descriptorRefjar-with-dependencies/descriptorRef /descriptorRefs archive manifest mainClasscontroller.HotelFrame/mainClass !-- 启动类 -- addClasspathtrue/addClasspath /manifest /archive /configuration executions execution idmake-assembly/id phasepackage/phase goals goalsingle/goal /goals /execution /executions /plugin执行mvn clean package生成target/hotel-system-1.0-jar-with-dependencies.jar。双击即可运行需系统已安装JRE或命令行java -jar target/hotel-system-1.0-jar-with-dependencies.jar关键参数mainClasscontroller.HotelFrame/mainClass指定启动类jar-with-dependencies打包所有依赖包括HikariCP、MySQL驱动避免NoClassDefFoundError。5.4 避坑Windows双击jar闪退与Mac菜单栏不显示的玄学问题现象 → 原因 → 解决专治平台特有问题现象Windows双击jar无反应命令行运行却正常原因JRE未关联jar文件类型或缺少javaw.exe无控制台窗口解决打包时用javaw启动并添加-Dfile.encodingUTF-8plugin groupIdorg.codehaus.mojo/groupId artifactIdexec-maven-plugin/artifactId version3.1.0/version configuration executablejavaw/executable !-- 关键 -- arguments argument-Dfile.encodingUTF-8/argument argument-jar/argument argument${project.build.directory}/${project.build.finalName}-jar-with-dependencies.jar/argument /arguments /configuration /plugin现象Mac上菜单栏File/Edit/Help不显示Swing原生菜单失效原因JVM未启用Apple扩展解决启动参数加-Dapple.laf.useScreenMenuBartruejava -Dapple.laf.useScreenMenuBartrue -jar hotel-system.jar现象Linux下字体模糊中文显示为方块原因OpenJDK缺少中文字体渲染支持解决打包时嵌入Noto Sans CJK字体Google开源// 在HotelFrame构造函数中 try { Font font Font.createFont(Font.TRUETYPE_FONT, getClass().getResourceAsStream(/fonts/NotoSansCJKsc-Regular.otf)); GraphicsEnvironment ge GraphicsEnvironment.getLocalGraphicsEnvironment(); ge.registerFont(font); UIManager.put(Label.font, new Font(Noto Sans CJK SC, Font.PLAIN, 12)); } catch (Exception e) { logger.warn(字体加载失败使用系统默认字体, e); }现象打包后off.bmp图片找不到界面按钮图标消失原因ImageIcon用new ImageIcon(off.bmp)是相对路径jar内需用类路径解决改为getClass().getResource(/images/off.bmp)ImageIcon icon new ImageIcon( getClass().getResource(/images/off.bmp) // 放在src/main/resources/images/ );6. 验证系统健壮性的五个硬核技巧从单元测试到压力摸底写完代码不等于做完得用真实场景锤炼它。我给自己立过规矩任何SwingMySQL项目上线前必须过这五关。不是为了炫技是避免凌晨三点被老板电话叫醒修Bug。6.1 用JUnit 5写DAO层单元测试隔离数据库验证SQL逻辑RoomDAOTest.java示例用H2内存数据库模拟MySQLExtendWith(MockitoExtension.class) class RoomDAOTest { private RoomDAO roomDAO; private Connection connection; BeforeEach void setUp() throws SQLException { // 启动H2内存数据库 connection DriverManager.getConnection(jdbc:h2:mem:test;DB_CLOSE_DELAY-1, sa, ); runScript(connection, src/test/resources/schema.sql); // 建表脚本 runScript(connection, src/test/resources/test-data.sql); // 测试数据 roomDAO new RoomDAO(connection); } Test void should_find_all_rooms() throws SQLException { // when ListRoom rooms roomDAO.findAll(); // then assertThat(rooms).hasSize(3); assertThat(rooms.get(0).getRoomId()).isEqualTo(101); assertThat(rooms.get(0).getStatus()).isEqualTo(空闲); } Test void should_update_room_status() throws SQLException { // given String roomId 101; // when roomDAO.updateStatus(roomId, 已预订); Room updated roomDAO.findById(roomId); // then assertThat(updated.getStatus()).isEqualTo(已预订); } }为什么用H2不用MockitoMockito只能模拟方法返回值无法验证SQL语句是否真能执行。H2内存库能跑真实SQL且启动快毫秒级是DAO测试黄金标准。BeforeEach确保每个测试用干净数据库。6.2 用JMeter模拟并发预订压测连接池与事务创建JMeter测试计划线程组10个线程循环100次HTTP请求实际用JSR223 Sampler调用Javaimport controller.EngageService; EngageService service new EngageService(); service.bookRoom(101, G001, new Date());查看结果树检查是否出现SQLException: Lock wait timeout exceeded关键指标平均响应时间 500ms合格错误率 0%必须连接池活跃数峰值 ≤ 10验证maximumPoolSize生效参数说明Lock wait timeout是MySQL锁等待超时默认50秒。若压测中出现说明事务粒度太大如update整张表需优化为UPDATE room SET status? WHERE room_id?。6.3 用VisualVM监控Swing内存揪出隐藏的引用泄漏启动程序时加JVM参数java -XX:UseG1GC -Xms512m -Xmx1024m -Dcom.sun.management.jmxremote -jar hotel-system.jar用VisualVM连接后监视标签页观察堆内存曲线点击“执行垃圾回收”后是否回落类标签页筛选javax.swing.*看JFrame,JDialog实例数是否随窗口开关线性增减Profiler标签页采样“CPU”和“内存”定位JTable渲染热点典型泄漏信号JDialog实例数持续增长不随窗口关闭下降 → 忘了dispose()DefaultTableModel对象数暴涨 →setModel()频繁创建新对象未复用6.4 用MySQL慢查询日志定位性能瓶颈开启MySQL慢查询my.cnfslow_query_log ON slow_query_log_file /var/log/mysql/mysql-slow.log long_query_time 1 # 超过1秒记为慢查询 log_queries_not_using_indexes ON操作后查看日志# Time: 2023-10-05T08:32:14.123456Z # UserHost: root[root] localhost [127.0.0.1] Id: 12 # Query_time: 2.34567 p a hrefhttps://download.csdn.net/download/weixin_42679995/26741773 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
