影碟出租管理系统课设实战:Swing+MySQL从建表到事务的完整落地
简介这是一套面向高校学生与Java初学者的影碟出租管理系统课设源码采用Swing图形界面搭配MySQL数据库实现适合用于Java课程设计、毕业设计或编程练手。系统涵盖登录注册、影碟信息增删改查、租赁记录管理等典型业务模块界面风格朴素实用更贴近学生实际水平便于顺利通过课设考核。压缩包共49个文件约153KB包含8个Java源文件、20个class编译文件、8个xml配置、1个sql数据库脚本以及若干png界面截图源码与建库脚本齐全导入IDEA或Eclipse后按说明即可运行。目前已有136人学习关注。购买后可获得详细部署教程与三天售后服务帮助读者快速完成环境搭建、数据库导入与功能调试理解JDBC工具类封装、Swing事件处理与增删改查逻辑是一份拿来即用的完整课设参考方案。1. 影碟出租管理系统从 Swing 界面到 MySQL 落库课设怎么做出生产味很多 Java 课设的结局是界面能点、数据库能连、答辩能过但代码交上去自己都不想再看第二眼。影碟出租管理系统这个题目尤其典型——它天然包含会员、影片、租赁、归还、库存、逾期计费这几条业务线用 Swing 做桌面端、MySQL 做持久化几乎覆盖了 Java 基础、JDBC、面向对象编程和 GUI 事件驱动的全部考点。问题在于大部分实现把业务逻辑全塞进按钮的 actionPerformed 里SQL 字符串拼接满天飞库存扣减和归还回滚没有任何事务保护。这篇笔记就按一线做法把「影碟出租管理系统【GUI/SwingMySQL】」从建库建表、DAO 分层、Swing 事件绑定到事务与并发扣减一步步拆成能直接抄的落地路径。适合正在做 Java 课设、想拿高分又不想写垃圾代码的人也适合工作后回头补桌面端 JDBC 基本功的开发者。2. 建库建表与 JDBC 连接先把数据地基打对2.1 四张核心表的设计与字段取舍影碟出租的业务本质是「影片库存随租赁和归还双向变动」所以表结构必须能支撑库存的原子扣减和恢复。我一般会建四张表film影片、member会员、rental租赁记录、以及一张可选的 fine_rule逾期费率。影片表里 stock_total 和 stock_available 要分开前者是总库存后者是可借数量归还时恢复的是 stock_available而不是简单加一。租赁记录表必须记录 rent_time、due_time、return_time 和 statusstatus 用枚举值 0 表示在租、1 表示已还、2 表示逾期未还这样查询和计费都有依据。CREATE TABLE film ( film_id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(120) NOT NULL, category VARCHAR(40), stock_total INT NOT NULL DEFAULT 1, stock_available INT NOT NULL DEFAULT 1, daily_price DECIMAL(6,2) NOT NULL DEFAULT 2.00, INDEX idx_title (title) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE member ( member_id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(60) NOT NULL, phone VARCHAR(20) UNIQUE, balance DECIMAL(8,2) NOT NULL DEFAULT 0.00, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE rental ( rental_id BIGINT PRIMARY KEY AUTO_INCREMENT, film_id INT NOT NULL, member_id INT NOT NULL, rent_time DATETIME NOT NULL, due_time DATETIME NOT NULL, return_time DATETIME NULL, status TINYINT NOT NULL DEFAULT 0, fee DECIMAL(8,2) NOT NULL DEFAULT 0.00, CONSTRAINT fk_rental_film FOREIGN KEY (film_id) REFERENCES film(film_id), CONSTRAINT fk_rental_member FOREIGN KEY (member_id) REFERENCES member(member_id), INDEX idx_member_status (member_id, status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个参数值得说清楚。stock_available 默认值设 1 而不是 0是因为新建影片时至少有一张可借daily_price 用 DECIMAL(6,2) 而不是 FLOAT避免计费时出现 0.30000000000000004 这种浮点误差。rental 表用 BIGINT 主键是因为租赁流水增长快INT 在课设演示阶段够用但养成习惯没坏处。外键约束在课设里常被忽略但它能防止删除影片时留下孤儿租赁记录答辩时被问到数据一致性也有话可说。2.2 JDBC 连接与连接池的最小配置Swing 桌面端不像 Web 项目有容器管理连接很多人直接在每个按钮里 DriverManager.getConnection结果就是频繁建连、性能差、还容易忘记关闭。常见做法是引入一个轻量连接池课设里用 HikariCP 就够配置简单且稳定。下面是一个可直接复用的 DbUtil。import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import java.sql.Connection; import java.sql.SQLException; public final class DbUtil { private static final HikariDataSource DS; static { HikariConfig cfg new HikariConfig(); cfg.setJdbcUrl(jdbc:mysql://127.0.0.1:3306/dvd_shop?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8); cfg.setUsername(root); cfg.setPassword(your_password); cfg.setMaximumPoolSize(8); // 桌面端并发低8 足够 cfg.setMinimumIdle(2); cfg.setConnectionTimeout(3000); // 3 秒连不上就报错避免界面卡死 cfg.setPoolName(dvd-pool); DS new HikariDataSource(cfg); } public static Connection getConnection() throws SQLException { return DS.getConnection(); } private DbUtil() {} }maximumPoolSize 设 8 是因为 Swing 桌面端同时操作数据库的线程通常只有事件分发线程和少量后台任务设太大反而浪费。connectionTimeout 设 3000 毫秒很关键——如果 MySQL 没启动界面不会无限期卡住而是三秒后抛异常你可以在 catch 里弹 JOptionPane 提示用户检查数据库服务。useSSLfalse 和 serverTimezone 是 MySQL 8 驱动常见报错的两个源头前者避免 SSL 握手警告后者避免时区导致的时间差 8 小时问题。characterEncodingutf8 保证中文影片名不乱码。2.3 建库脚本与初始化数据的执行顺序建表之后要插入初始数据顺序必须是先 film 和 member再 rental否则外键约束会直接拒绝插入。我一般把初始化脚本单独放一个 init.sql用 MySQL 命令行或 Workbench 执行。mysql -uroot -p dvd_shop init.sqlinit.sql 里先写 INSERT INTO film 若干条再写 INSERT INTO member最后如果需要演示租赁记录再插 rental。注意 rental 的 rent_time 和 due_time 要符合业务逻辑due_time 通常是 rent_time 加 7 天。如果初始化时 stock_available 和 stock_total 不一致后续扣减会直接翻车所以插入影片时两个字段要写成相同值。3. DAO 分层与业务方法把 SQL 从按钮里赶出去3.1 FilmDao 与 MemberDao 的接口划分Swing 界面里最忌讳的就是按钮回调里直接写 SQL。正确做法是每个实体一个 DAODAO 只负责单表 CRUD 和必要的查询跨表业务放到 Service 层。FilmDao 至少要有 findByTitle、findAvailable、decreaseStock、increaseStock 四个方法。decreaseStock 必须带条件 stock_available 0否则并发下会把库存扣成负数。public class FilmDao { public ListFilm findByTitle(String keyword) throws SQLException { String sql SELECT * FROM film WHERE title LIKE ? ORDER BY film_id DESC; ListFilm list new ArrayList(); try (Connection conn DbUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } } return list; } public int decreaseStock(Connection conn, int filmId) throws SQLException { String sql UPDATE film SET stock_available stock_available - 1 WHERE film_id ? AND stock_available 0; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, filmId); return ps.executeUpdate(); // 返回 0 表示库存不足 } } private Film mapRow(ResultSet rs) throws SQLException { Film f new Film(); f.setFilmId(rs.getInt(film_id)); f.setTitle(rs.getString(title)); f.setStockAvailable(rs.getInt(stock_available)); f.setDailyPrice(rs.getBigDecimal(daily_price)); return f; } }decreaseStock 接收外部传入的 Connection而不是自己从 DbUtil 取这是为了让它能参与 Service 层的事务。如果 DAO 自己开连接事务就没法跨多个 DAO 方法。返回值为 0 时Service 层要抛出业务异常并回滚界面层捕获后提示「该影片已无可借库存」。findByTitle 用 LIKE 配合参数化查询既支持模糊搜索又避免 SQL 注入这是课设答辩常问的点。3.2 RentalService 的事务边界与逾期计费租赁和归还是两个必须用事务包裹的操作。租赁时要同时做三件事插入 rental 记录、扣减 film 库存、必要时扣会员余额如果收押金。归还时要更新 rental 的 return_time 和 status、恢复库存、计算逾期费并扣会员余额。任何一步失败都要整体回滚。public class RentalService { private final FilmDao filmDao new FilmDao(); private final RentalDao rentalDao new RentalDao(); private final MemberDao memberDao new MemberDao(); public void rent(int filmId, int memberId, int days) throws SQLException { Connection conn null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); int updated filmDao.decreaseStock(conn, filmId); if (updated 0) { throw new BusinessException(库存不足无法租赁); } LocalDateTime now LocalDateTime.now(); rentalDao.insert(conn, filmId, memberId, now, now.plusDays(days)); conn.commit(); } catch (Exception e) { if (conn ! null) conn.rollback(); throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } } public BigDecimal returnFilm(long rentalId) throws SQLException { Connection conn null; try { conn DbUtil.getConnection(); conn.setAutoCommit(false); Rental r rentalDao.findById(conn, rentalId); if (r null || r.getStatus() ! 0) { throw new BusinessException(该租赁记录不存在或已归还); } LocalDateTime now LocalDateTime.now(); long overdueDays Math.max(0, ChronoUnit.DAYS.between(r.getDueTime(), now)); BigDecimal fee r.getDailyPrice() .multiply(BigDecimal.valueOf(overdueDays)); rentalDao.markReturned(conn, rentalId, now, fee); filmDao.increaseStock(conn, r.getFilmId()); if (fee.compareTo(BigDecimal.ZERO) 0) { memberDao.deductBalance(conn, r.getMemberId(), fee); } conn.commit(); return fee; } catch (Exception e) { if (conn ! null) conn.rollback(); throw e; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } } }事务边界放在 Service 层DAO 只接收 Connection这是最清晰的分工。逾期天数用 ChronoUnit.DAYS.between 计算注意它返回的是完整天数如果 due_time 是 7 天前但不足 8 天返回 7符合「逾期一天收一天」的常规逻辑。fee 用 BigDecimal 乘法避免 double 精度问题。归还时先查记录再判断 status防止重复归还导致库存被多次恢复——这是课设里最常见的 bug 之一。3.3 会员余额扣减与并发安全会员余额扣减也要用带条件的 UPDATE防止余额被扣成负数。如果课设不要求透支就加 balance ? 条件。public int deductBalance(Connection conn, int memberId, BigDecimal amount) throws SQLException { String sql UPDATE member SET balance balance - ? WHERE member_id ? AND balance ?; try (PreparedStatement ps conn.prepareStatement(sql)) { ps.setBigDecimal(1, amount); ps.setInt(2, memberId); ps.setBigDecimal(3, amount); return ps.executeUpdate(); } }返回 0 表示余额不足Service 层应抛出异常并回滚整个归还事务。这样即使逾期费计算正确余额不够也不会把库存恢复和租赁状态更新提交上去保证数据一致。Swing 桌面端虽然并发低但同一台机器上多个窗口同时操作同一会员是可能的带条件的 UPDATE 是最后一道防线。4. Swing 界面与事件绑定让按钮真正驱动业务4.1 主窗口布局与表格模型Swing 做管理系统的经典布局是顶部搜索栏、中部 JTable 展示影片或租赁记录、底部操作按钮。JTable 必须配一个自定义 TableModel而不是直接用 Object[][]否则数据刷新时容易出问题。public class FilmTableModel extends AbstractTableModel { private final String[] columns {ID, 片名, 分类, 可借, 日租金}; private ListFilm data new ArrayList(); public void setData(ListFilm data) { this.data data; fireTableDataChanged(); } Override public int getRowCount() { return data.size(); } Override public int getColumnCount() { return columns.length; } Override public String getColumnName(int col) { return columns[col]; } Override public Object getValueAt(int row, int col) { Film f data.get(row); switch (col) { case 0: return f.getFilmId(); case 1: return f.getTitle(); case 2: return f.getCategory(); case 3: return f.getStockAvailable(); case 4: return f.getDailyPrice(); default: return ; } } }setData 里调用 fireTableDataChanged 而不是 fireTableStructureChanged前者只刷新数据不重置列宽和排序状态用户体验更好。搜索按钮的回调里调用 FilmDao.findByTitle拿到 List 后调用 model.setData表格自动更新。注意 Swing 的 UI 更新必须在事件分发线程EDT上执行如果搜索走后台线程要用 SwingUtilities.invokeLater 包一层。4.2 租赁与归还按钮的完整事件流租赁按钮的 actionPerformed 里要做四件事从表格获取选中影片 ID、从输入框获取会员 ID 和租赁天数、调用 RentalService.rent、根据结果弹提示并刷新表格。rentButton.addActionListener(e - { int row filmTable.getSelectedRow(); if (row 0) { JOptionPane.showMessageDialog(this, 请先选择影片); return; } int filmId (int) filmTableModel.getValueAt(row, 0); String memberText memberField.getText().trim(); String daysText daysField.getText().trim(); if (memberText.isEmpty() || daysText.isEmpty()) { JOptionPane.showMessageDialog(this, 会员 ID 和天数不能为空); return; } try { int memberId Integer.parseInt(memberText); int days Integer.parseInt(daysText); rentalService.rent(filmId, memberId, days); JOptionPane.showMessageDialog(this, 租赁成功); refreshFilmTable(); } catch (NumberFormatException ex) { JOptionPane.showMessageDialog(this, 会员 ID 和天数必须是数字); } catch (BusinessException ex) { JOptionPane.showMessageDialog(this, ex.getMessage()); } catch (SQLException ex) { JOptionPane.showMessageDialog(this, 数据库错误 ex.getMessage()); } });这里把 NumberFormatException 和 BusinessException 分开捕获是为了给用户不同的提示。数据库异常单独捕获并显示 message方便排查连接问题。refreshFilmTable 重新查询并 setData保证库存数字实时更新。归还按钮类似但需要从租赁记录表格获取 rentalId调用 returnFilm 后把返回的逾期费显示出来。4.3 界面刷新与 EDT 线程规则Swing 的线程规则只有一条所有 UI 操作在 EDT 上所有耗时操作在后台线程。数据库查询如果放在 EDT 上MySQL 慢的时候界面会假死。常见做法是用 SwingWorker。new SwingWorkerListFilm, Void() { Override protected ListFilm doInBackground() throws Exception { return filmDao.findByTitle(keyword); } Override protected void done() { try { filmTableModel.setData(get()); } catch (Exception ex) { JOptionPane.showMessageDialog(null, 查询失败 ex.getMessage()); } } }.execute();doInBackground 在后台线程执行 SQLdone 在 EDT 上执行get 拿到结果后更新表格。这样即使数据库响应慢界面也不会卡住。课设演示时如果评委点搜索按钮界面卡三秒印象分直接掉用 SwingWorker 是最低成本的修复。5. 避坑与排查课设里最容易翻车的五个点5.1 库存扣成负数现象两部影片库存都是 1两个窗口同时租赁结果 stock_available 变成 -1。原因先 SELECT 查库存再 UPDATE 扣减两步之间没有锁。解决用带条件的 UPDATEWHERE stock_available 0根据 executeUpdate 返回值判断是否成功返回 0 就抛异常回滚。5.2 中文影片名乱码现象数据库里存进去是问号或者界面显示乱码。原因JDBC URL 没加 characterEncodingutf8或者建表时用了 latin1。解决URL 加 characterEncodingutf8建表和数据库都用 utf8mb4MySQL 配置文件里 character-set-serverutf8mb4。5.3 归还后库存没恢复现象归还成功提示弹了但影片可借数量没变。原因markReturned 和 increaseStock 不在同一个事务里或者 increaseStock 的 filmId 传错。解决确认两个操作共用同一个 Connection 且 autoCommit 为 false归还前先查 rental 记录拿到正确的 filmId。5.4 时间差 8 小时现象rent_time 存进去比实际时间少 8 小时。原因JDBC URL 没设 serverTimezone驱动用了 UTC。解决URL 加 serverTimezoneAsia/Shanghai或者 MySQL 全局设置 time_zone08:00。5.5 连接池耗尽现象操作几次后界面报「Connection is not available」。原因DAO 里拿了连接没关闭或者异常路径下没走 finally。解决所有 Connection、PreparedStatement、ResultSet 都用 try-with-resourcesService 层的事务连接在 finally 里 close。6. 进阶技巧用存储过程做逾期批量计费与对账课设做到基本功能后如果想再拉一点区分度可以把逾期计费做成 MySQL 存储过程每天定时跑一次把所有 status0 且 due_time 小于当前时间的记录标记为逾期并累加费用。这样租赁表里多一个 overdue_fee 字段归还时直接读不用每次重算。DELIMITER // CREATE PROCEDURE mark_overdue() BEGIN UPDATE rental r JOIN film f ON r.film_id f.film_id SET r.status 2, r.fee r.fee f.daily_price * DATEDIFF(NOW(), r.due_time) WHERE r.status 0 AND r.due_time NOW(); END // DELIMITER ;调用方式是 CALL mark_overdue()可以在系统启动时用 Java 的 ScheduledExecutorService 每天凌晨执行一次。注意这个存储过程会把 fee 累加所以归还时不能再重复计算逾期费否则会收两次钱。我一般会在 rental 表加一个 fee_settled 标志位归还时如果 status2 就直接读 fee如果 status0 才现算。对账功能可以用一条 SQL 查出所有未归还且已逾期的记录和会员余额做交叉验证。SELECT m.name, m.balance, r.rental_id, r.fee, f.title FROM rental r JOIN member m ON r.member_id m.member_id JOIN film f ON r.film_id f.film_id WHERE r.status IN (0, 2) ORDER BY r.due_time;这条查询在答辩演示时很加分能直观展示「谁欠费、欠多少、哪部片子」。我自己的习惯是课设代码写完先跑一遍这个对账 SQL如果余额和费用对不上说明事务或计费逻辑有漏洞比界面点一百次都管用。希望帮到你。本文还有配套的精品资源点击获取