ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Java Swing+JDBC的电影管理系统课设实战与避坑指南

基于Java Swing+JDBC的电影管理系统课设实战与避坑指南 简介一份基于Java开发的简单电影管理系统源码整合了JSP、Servlet、JavaScript、CSS、HTML及PHP等Web技术适合Java初学者、课程设计或小型电影资料库管理场景。系统覆盖电影信息录入、查询、管理和展示等核心功能支持电影海报与简介展示前后端结构完整能帮助读者理解JavaWeb项目从页面交互到数据库操作的完整链路。压缩包共236个文件大小约16.45MB包含Java源码、JSP页面、SQL脚本、JS脚本、CSS样式、字体图标及大量PNG图片素材目录按Web工程常见结构组织便于对照学习。目前已有301人学习下载。作为完整可运行的源码工程既可用作毕业设计或实训参考也能在其基础上扩展搜索、评分、评论等模块是快速上手JavaWeb开发的实用素材。1. 基于Java的简单电影管理系统课设源码到底要写成什么样「基于 Java 的简单电影管理系统」大概是 Java 课程设计里出现频率最高的题目之一网上的设计源码也最多但质量参差有的只有两个类硬凑有的把 SQL 直接写在按钮监听里拷下来根本跑不起来。这个项目真正要交付的是一套能现场演示的登录、电影增删改查、模糊搜索和订单记录外加一份讲得清楚的分层代码结构。这篇笔记按我实际交付课设源码的路径来讲选型、建表、DAO 层、界面联动再到验收前最容易翻车的五个地方。适合正在做 Java 课设、或者想拿这个项目练 JDBC 与 Swing 的人照着能复现也能应付答辩追问。2. 技术选型和项目骨架为什么 Swing JDBC 是课设的稳妥答案拿到题目先别急着写代码。电影管理系统可用的技术栈其实不少但标题里「简单」两个字已经把范围框死了。我的判断是 Swing MySQL JDBC界面用桌面窗口数据落 MySQLJDBC 做桥。这套组合最大的优点是老师拿到源码后不需要装 Tomcat、不需要配 Node只要 JDK 和 MySQL 就能跑缺点也明显界面观感偏老但课设阶段完全够用。2.1 三种常见技术栈的取舍课设不是越新越好先看对比再选型技术栈上手门槛演示环境要求代码量答辩风险Swing JDBC MySQL低JDK 8、MySQL中低原理好讲JavaFX JDBC中JDK 8 或额外装 OpenJFX中中版本坑多JSP/Servlet Tomcat中JDK、Tomcat、MySQL偏高中环境问题多Spring Boot Vue高JDK、Maven、Node高高明显超出课设范围JavaFX 在 JDK 8 里还是官方组件但从 JDK 11 开始被剥离需要单独引 OpenJFX 依赖很多同学在这一步就卡住了。JSP/Servlet 需要部署到 Tomcat一旦端口被占用或者 web.xml 写错演示现场就很难收场。Spring Boot Vue 当然更好看但对「简单电影管理系统」来说属于过度设计答辩时老师一句「这里为什么不用 JDBC 直连」就能把节奏带偏。所以我一般建议老老实实用 Swing。它虽然不那么现代但事件监听、布局管理、表格模型这些知识点都是 Java 基础课上的核心内容和标题里的「简单设计源码」定位完全匹配。常见做法是登录窗口一个 JFrame主界面一个 JFrame里面用 JTabbedPane 放电影管理、订单查询两个面板数据展示用 JTable。也别急着去网上下载现成源码改那些源码里充斥着乱码、驱动版本过时、没有分层的问题改起来比自己写还费劲。2.2 项目目录分层实体、DAO、Service、UI 四层怎么分网上能搜到的 Java 课程设计案例源码里分层混乱是通病。很多只有一个 Main 类加一个 Connection 工具类数据库操作全写在按钮的 ActionListener 里。这种写法跑通很容易但答辩时几乎必被问「如果我要加一个会员表你的代码要改几个地方」。所以哪怕项目再简单我也建议按四层组织movie-system/ ├── src/ │ └── com/course/movie/ │ ├── entity/ Movie.java User.java Order.java │ ├── dao/ MovieDao.java UserDao.java OrderDao.java │ ├── service/ MovieService.java UserService.java OrderService.java │ ├── ui/ LoginFrame.java MainFrame.java │ └── util/ DBUtil.java MD5Util.java ├── sql/ │ └── init.sql # 建库建表 样例数据 └── lib/ └── mysql-connector-java-8.0.33.jarentity 里只放字段和 getter/setter对应数据库表dao 只写 SQL一个方法对应一条语句service 做业务判断比如价格不能为负、库存不能超卖ui 只负责界面和事件不直接拼 SQL。这样分层之后面向对象编程的好处才体现得出来数据、逻辑、展示互相解耦哪一层出问题就只改哪一层。一个容易忽略的细节是包名。我见过不少源码把类直接放在默认包下这在命令行编译时会很痛苦而且没法用包级私有访问。从第一天就用com.course.movie这种带反域名规则的包名之后无论是打 jar 还是写测试都顺很多。实体类的字段尽量和数据库列名一一对应下划线转驼峰减少 DAO 层里手动映射的出错概率。2.3 依赖与运行环境JDK 版本、MySQL 驱动和兼容性如果课程允许用 Mavenpom.xml 里只需要一个运行时依赖dependencies dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency /dependencies如果老师要求不联网、全程命令行演示那就下载驱动 jar 放进 lib 目录编译运行靠 classpath 指进来javac -encoding UTF-8 -cp lib/*;src src/com/course/movie/ui/Main.java java -cp lib/*;src com.course.movie.ui.Main这里有个非常典型的坑MySQL 5.x 和 8.x 的驱动类名不一样。MySQL 5.x 用com.mysql.jdbc.DriverMySQL 8.x 必须用com.mysql.cj.jdbc.Driver。很多老源码里还写着旧类名连 MySQL 8 时直接报 ClassNotFoundException这不是代码错了是版本没对齐。驱动 jar 的版本不要求和 MySQL 服务端完全一致但 5.1.x 的旧驱动连 MySQL 8 会遇到caching_sha2_password认证报错反过来 8.x 驱动连 5.7 倒是常见没问题。所以最简单的方案是统一用 8.0.x 驱动不管服务端是 5.7 还是 8.0。JDK 方面建议用 8 或 11Swing 在 8 上最稳11 也兼容但别用太新的 JDK 版本去跑跨大版本的课设代码编译选项经常会引入额外问题。提示如果开发环境没联网提前把驱动 jar 拷到 lib 目录并在 IDEA 的 Project Structure - Modules - Dependencies 里把它标记为 Library否则 IDEA 里能编译、命令行里java -cp却找不到类。3. 数据库设计与 JDBC 封装把电影表的基础操作一次写对技术骨架定下来之后真正的工程量在数据库和 DAO 层。这个环节决定了一件事后面界面写起来是顺滑还是到处补丁。我的习惯是先把 SQL 脚本写完、跑通再写 Java 代码因为 DAO 里的每个方法都是一条 SQL 的映射SQL 设计烂了Java 层怎么补都别扭。3.1 建表 SQL电影、用户、订单三张表的最小设计「简单」不等于只建一张电影表。一个能上台面的电影管理系统至少要有电影表、用户表、订单表。用户表解决登录订单表把“谁、在什么时候、买了哪部电影”记下来这样答辩时才有东西可讲。CREATE DATABASE IF NOT EXISTS movie_system DEFAULT CHARACTER SET utf8mb4; USE movie_system; CREATE TABLE t_movie ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, genre VARCHAR(30), director VARCHAR(50), release_date DATE, duration INT DEFAULT 120, price DECIMAL(6,2) NOT NULL DEFAULT 0, stock INT NOT NULL DEFAULT 0, description VARCHAR(500) ); CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password CHAR(32) NOT NULL, role VARCHAR(20) DEFAULT STAFF ); CREATE TABLE t_order ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, movie_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, order_time DATETIME NOT NULL, CONSTRAINT fk_order_user FOREIGN KEY (user_id) REFERENCES t_user(id), CONSTRAINT fk_order_movie FOREIGN KEY (movie_id) REFERENCES t_movie(id) );几个字段设计上的说明price用 DECIMAL 而不是 DOUBLE因为浮点数存金额会出现 0.1 0.2 不等于 0.3 的精度问题这在财务相关字段上是原则性的release_date用 DATE 类型Java 侧对应java.sql.Date后面避坑章会专门讲它和java.util.Date的转换stock是库存余量下单时减一这个字段是后面事务演示的抓手。password CHAR(32)对应 MD5 的 32 位十六进制输出如果后面改用 SHA-256 就要改成 CHAR(64)。外键在课设里建议保留。虽然性能上有争议但老师看到表结构里有外键和索引至少知道你有数据库设计的意识。样例数据我一般会插 5 部不同类型的电影年份拉开、价格有高有低这样演示「按类型筛选」「按价格排序」时不用现场现改数据。插入样例时注意先插入 t_user再插入 t_movie最后插入 t_order否则外键约束会直接报错。3.2 DBUtil 封装连接 URL 那一串参数到底在防什么JDBC 连接代码是网上下载源码里问题最多的部位最大的问题不是语法而是把连接参数写死在每个方法里。我见过的课设里十个有八个在每次查询时重新DriverManager.getConnection窗口一开就打开了几十个连接数据库连接数直接被耗尽。正确做法是封装一个 DBUtilpackage com.course.movie.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL jdbc:mysql://localhost:3306/movie_system ?useUnicodetruecharacterEncodingutf8 useSSLfalseserverTimezoneAsia/Shanghai allowPublicKeyRetrievaltrue; private static final String USER root; private static final String PASSWORD 123456; static { try { Class.forName(com.mysql.cj.jdbc.Driver); } catch (ClassNotFoundException e) { throw new RuntimeException(MySQL 驱动加载失败请检查 lib 目录, e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }静态代码块保证驱动类只加载一次。URL 后面四个参数每个都有实际意义characterEncodingutf8配合建库时的utf8mb4解决中文乱码useSSLfalse关闭 SSL 握手本地开发能省掉一堆证书告警日志serverTimezoneAsia/Shanghai解决 MySQL 8 报的The server time zone value错误这个报错信息在中文系统里经常变成乱码新手很容易误判成驱动问题allowPublicKeyRetrievaltrue是 MySQL 8 用caching_sha2_password插件时客户端首次连接需要的参数不加会报Public Key Retrieval is not allowed。这里顺带说明白一个 Java 基础考点Class.forName的作用是触发驱动类的静态初始化8.x 驱动其实在 JDBC 4.0 之后支持 SPI 自动注册不写这行也能工作但课设里写上更稳妥也方便被问到「驱动是怎么加载的」时有个标准答案。3.3 MovieDao 核心方法PreparedStatement 的参数和返回值DAO 层里电影表的增删改查是整套源码的主干。以添加和条件查询为例public int addMovie(Movie m) throws SQLException { String sql INSERT INTO t_movie(title, genre, director, release_date, duration, price, stock, description) VALUES(?,?,?,?,?,?,?,?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setString(1, m.getTitle()); ps.setString(2, m.getGenre()); ps.setString(3, m.getDirector()); ps.setDate(4, new java.sql.Date(m.getReleaseDate().getTime())); ps.setInt(5, m.getDuration()); ps.setBigDecimal(6, m.getPrice()); ps.setInt(7, m.getStock()); ps.setString(8, m.getDescription()); ps.executeUpdate(); try (ResultSet rs ps.getGeneratedKeys()) { return rs.next() ? rs.getInt(1) : -1; } } } public ListMovie findMovies(String keyword) throws SQLException { String sql SELECT * FROM t_movie WHERE title LIKE ? ORDER BY id DESC; ListMovie 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()) { Movie m new Movie(); m.setId(rs.getInt(id)); m.setTitle(rs.getString(title)); m.setGenre(rs.getString(genre)); m.setDirector(rs.getString(director)); m.setReleaseDate(rs.getDate(release_date)); m.setPrice(rs.getBigDecimal(price)); m.setStock(rs.getInt(stock)); list.add(m); } } } return list; }代码里有三个值得在答辩时展开的点。第一PreparedStatement用?占位并在setXxx时指定类型除了防 SQL 注入还能避免字符串拼接时引号转义的麻烦第二RETURN_GENERATED_KEYS让我们在插入后拿到自增主键这是订单表关联电影时必须的能力第三try-with-resources会自动关闭 Connection、Statement、ResultSet传统写法里忘记关闭 ResultSet 会导致连接泄漏泄漏到一定程度就会出现「系统跑着跑着突然全部请求超时」的诡异现象。rs.getDate(release_date)返回java.sql.Date而实体类里如果用java.util.Date声明赋值时建议像代码里那样在 set 时显式转换或者实体类直接声明成java.sql.Date能少掉一半转换代码。模糊查询的LIKE参数写法是% keyword %占位符只传一个拼接好的字符串不要自己在外层拼 SQL 再传进去那样又回到了注入的坑里。3.4 Service 层业务校验与下单事务顺带解决数据一致性网上源码大多只有 DAO 和界面两层Service 通常是空的。但加上一个薄薄的 Service 层代码档次马上不一样。它负责 DAO 不负责的事校验、事务、业务规则。以「用户购票下单」为例这个动作要同时做两件事往 t_order 插一条记录把 t_movie 的 stock 减一。两步必须同时成功或同时失败否则会出现「订单记录了但库存没扣」的数据不一致。public boolean orderMovie(int userId, int movieId, int quantity) { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 关闭自动提交开启事务 try (PreparedStatement ps1 conn.prepareStatement( INSERT INTO t_order(user_id, movie_id, quantity, order_time) VALUES(?,?,?,NOW()))) { ps1.setInt(1, userId); ps1.setInt(2, movieId); ps1.setInt(3, quantity); ps1.executeUpdate(); } try (PreparedStatement ps2 conn.prepareStatement( UPDATE t_movie SET stock stock - ? WHERE id ? AND stock ?)) { ps2.setInt(1, quantity); ps2.setInt(2, movieId); ps2.setInt(3, quantity); if (ps2.executeUpdate() 0) { conn.rollback(); // 库存不足回滚订单 return false; } } conn.commit(); return true; } catch (SQLException e) { try { if (conn ! null) conn.rollback(); } catch (SQLException ex) { } return false; } finally { try { if (conn ! null) conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { } } }这段代码直接用一条带stock ?条件的 UPDATE 来防超卖比先 SELECT 再 UPDATE 更安全因为它在数据库层面保证了原子性。setAutoCommit(false)之后两条 SQL 共享同一个 Connection 上的事务任何一条失败就 rollback。这个写法在「java 怎么保证数据一致性」这类问题下是标准答案之一要么用事务要么用数据库约束兜底。提示事务治理的关键是 Connection 必须从外面传进来不能在每个 DAO 方法内部各自 getConnection。否则两条 SQL 各开各的连接事务根本不存在。常见做法是 Service 层获取连接、开启事务、把 Connection 作为参数传给 DAO 方法。4. 登录鉴权与界面联动把登录框到主界面的链路一次打通数据库和 DAO 层完成后系统的核心能力已经有了接下来是界面。这一章最容易被低估因为很多人以为就是拖控件。实际上课设答辩时老师点开程序第一个操作就是登录登录进不去后面全白搭。而且登录不是简单比对用户名密码还牵涉到密码存储、会话传递、权限控制三个问题。4.1 密码存储MD5 加盐而不是明文比对用户表的 password 字段设计成 CHAR(32) 就是为了存 MD5 摘要。登录校验时把用户输入的密码做一次 MD5再和库里存的值比对。这样即使数据库文件被拷走密码原文也不会直接泄露。public static String md5(String input) { try { MessageDigest md MessageDigest.getInstance(MD5); byte[] digest md.digest(input.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : digest) { sb.append(String.format(%02x, b)); // 每个字节补成两位十六进制 } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }这里用String.format(%02x, b)把每个字节转成两位十六进制拼出来正好 32 位。要注意input.getBytes()不指定编码时依赖平台默认字符集跨系统部署可能出现同一密码摘要不同的怪事所以固定用 UTF-8。登录查询的 SQL 是SELECT * FROM t_user WHERE username? AND password?配合 PreparedStatement 占位符就把经典的 or 11注入挡在门外。提示MD5 已经不算安全加密算法容易被彩虹表碰撞课设里为了讲清楚原理可以用。如果想让答辩多一个亮点可以在 MD5 前加固定盐md5(username password movie_salt)并在初始化管理员账号时用同样的规则生成摘要。4.2 主界面与表格刷新JTable 数据不更新的根因主界面最核心的组件是 JTable。常见做法是给 JTable 配一个 DefaultTableModel把数据库里查出来的 List 转成二维数组填进去。很多源码在这个环节翻车点完「新增」按钮后数据库里确实多了一行但界面纹丝不动必须重启程序才能看到。private void refreshTable(String keyword) { try { ListMovie movies movieService.findMovies(keyword); String[] columns {ID, 片名, 类型, 导演, 上映日期, 时长, 票价, 库存}; Object[][] rows new Object[movies.size()][columns.length]; for (int i 0; i movies.size(); i) { Movie m movies.get(i); rows[i][0] m.getId(); rows[i][1] m.getTitle(); rows[i][2] m.getGenre(); rows[i][3] m.getDirector(); rows[i][4] m.getReleaseDate() null ? : m.getReleaseDate().toString(); rows[i][5] m.getDuration(); rows[i][6] m.getPrice(); rows[i][7] m.getStock(); } table.setModel(new DefaultTableModel(rows, columns)); // 整体替换模型触发重绘 } catch (SQLException e) { JOptionPane.showMessageDialog(this, 查询失败 e.getMessage()); } }关键在最后一行的setModelJTable 的视图模型被整体替换界面才会触发重绘。如果只改数据源而不重新 setModelJTable 根本不知道数据变了。建议把这段逻辑收成一个refreshTable(String keyword)方法所有按钮的事件回调里统一调用它。另一个相关细节是DefaultTableModel 默认不允许编辑单元格如果想让双击单元格直接改库存需要重写isCellEditable方法返回 true否则界面看得到改不了。4.3 事件监听与窗口跳转登录状态如何带到主界面登录成功后要做的事情不是简单new MainFrame().setVisible(true)而是把当前登录用户传过去。常见做法是给 MainFrame 加一个带 User 参数的构造器loginBtn.addActionListener(e - { String username userField.getText().trim(); String password new String(passField.getPassword()); User user userService.login(username, password); if (user null) { JOptionPane.showMessageDialog(LoginFrame.this, 用户名或密码错误); return; } LoginFrame.this.dispose(); // 释放登录窗口 new MainFrame(user).setVisible(true); // 把登录用户带进主界面 });这里有两个容易被答辩追问的细节。第一密码框要用JPasswordField而不是 JTextField用getPassword()返回 char 数组而不是字符串这是 Swing 的安全惯例虽然课设里很多人嫌麻烦直接用 JTextField。第二登录成功后用dispose()而不是setVisible(false)关闭登录窗口否则 JVM 里残留的隐式窗口会让程序退出时卡在后台任务管理器里看到 java 进程一直不消失。权限控制可以在 MainFrame 构造器里做根据user.getRole()判断「管理员」还是「员工」管理员显示删除和订单管理按钮员工只能查询和购票。这是答辩的加分点也是业务系统里最基础的权限模型。具体实现只要一行deleteBtn.setVisible(ADMIN.equals(user.getRole()))不需要引入任何框架。4.4 订单查询面板关联查询的结果映射订单面板要展示的不只是 t_order 表本身而是订单、用户名、电影名三张表 join 后的结果。SQL 写成SELECT o.id, u.username, m.title, o.quantity, o.order_time FROM t_order o JOIN t_user u ON o.user_id u.id JOIN t_movie m ON o.movie_id m.id ORDER BY o.order_time DESC;这属于 Java 基础的 JDBC 结果映射练习ResultSet 里同时存在 u.username 和 m.title字段名不会冲突直接用rs.getString(username)和rs.getString(title)取值即可。唯一要注意的是如果 SQL 里有重名字段比如两张表都有 id必须在 SELECT 里用别名区分否则rs.getInt(id)会取到不确定的一列。这个面板建议放在 JTabbedPane 的第二个页签里和电影管理页签分开界面逻辑更清晰。5. 避坑记录验收前最容易翻车的五个地方这一章全部是血泪经验。前四章的东西照着写都能跑但课设演示崩掉的几乎都是下面这几类问题。每一条都按「现象 → 原因 → 解决」的顺序写方便在交付前自己排查一遍。5.1 中文乱码界面和数据库里全是问号现象程序能跑但界面上的标题、数据库里的电影名全是 ?? 或乱码控制台输出中文也偶尔错位。原因三个环节的字符集不一致——Java 源码文件编译编码、JDBC 连接字符集、数据库表字符集。最常见的组合是源码用 GBK 保存、连接串没写 characterEncoding、表又是 latin1 默认字符集。解决把三处统一成 UTF-8。IDEA 里 Settings - File Encodings 改成 UTF-8MySQL 建库用DEFAULT CHARACTER SET utf8mb4JDBC URL 加characterEncodingutf8。命令行编译时加-encoding UTF-8。改完这三处乱码基本绝迹。建库时如果用 Navicat 手工建库记得把字符集下拉框从默认改成 utf8mb4排序规则选 utf8mb4_general_ci 即可。5.2 ClassNotFoundException驱动类找不到现象点登录按钮时抛java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver或者直接 NoClassDefFoundError。原因驱动 jar 没在运行 classpath 里。IDEA 里编译通过但运行时找不到通常是 jar 只在编译期被引用没有作为 Library 加入模块依赖命令行运行时则是没写-cp lib/mysql-connector-java-8.0.33.jar。解决IDEA 里 File - Project Structure - Modules - Dependencies 添加 jar命令行用java -cp lib/*;target/classes com.course.movie.ui.Main的写法lib/*会把目录下所有 jar 都加进来。另外检查驱动类名MySQL 8 用com.mysql.cj.jdbc.Driver老源码里的com.mysql.jdbc.Driver在 8.x 包里已经被移除。5.3 JTable 不刷新新增、删除后界面纹丝不动现象数据库里数据已经变了JTable 还是显示旧数据重启程序才正常。原因只是改了内部数据源没有通知 JTable 重新绘制。DefaultTableModel 的数据变化需要触发 table 结构更新或者干脆整体替换 model 对象。解决统一走refreshTable()方法内部调用table.setModel(new DefaultTableModel(rows, columns))。如果用的是tableModel.addRow()这种增量写法记得在批量操作后调用model.fireTableDataChanged()否则 UI 线程不知道数据变化事件通知机制没生效。5.4 日期转换java.util.Date 和 java.sql.Date 的类型冲突现象实体类用java.util.Date声明 releaseDateDAO 层ps.setDate报编译错误或者界面显示日期变成带时分秒的一长串。原因PreparedStatement.setDate只接受java.sql.Date而实体类里导入的全是java.util.Date两个类都叫 Date写代码时很容易引错包。反过来从 ResultSet 读出的 sql.Date 直接塞进 util.Date 字段行为诡异但能编译。解决实体类字段直接声明成java.sql.Date或者 DAO 层显式转换new java.sql.Date(m.getReleaseDate().getTime())。显示时统一date.toString()输出 yyyy-MM-dd 格式不要额外拼格式化逻辑。如果哪天改成 LocalDate就要配套用rs.getObject(release_date, LocalDate.class)这套新 API 会干净很多。5.5 SQL 注入输入 or 11直接登录成功现象用户名输入admin or 11密码随便填居然登录成功了。原因登录 SQL 是字符串拼接SELECT * FROM t_user WHERE username username AND password password 。输入内容把 SQL 的引号闭合再用恒真条件绕过密码校验。Statement 拼 SQL 的写法天然有这个问题不光是登录任何查询都可能被注入。解决全部改用 PreparedStatement 参数占位。这不仅是安全修复还能让代码可读性变好。如果老师追问「怎么从源头防范」答案就三条参数化查询、输入校验username 限制长度和字符集、数据库账号最小权限。课设里做到第一条就足够但能把三条都说出来答辩印象分会高不少。6. 验收演示与两个加分扩展从「跑通」到「敢答辩」系统能跑起来只是及格线验收现场才是真正的考验。我的经验是不要等老师随机点自己设计一条有逻辑的演示路径把每个功能都走到同时把数据库的坑提前填平。6.1 十分钟验收流程按这个顺序演示最稳我一般按下面这个顺序走每步都有明确的预期结果先故意输错一次密码确认弹「用户名或密码错误」证明校验生效再用管理员账号登录切换到员工账号登录确认删除按钮被隐藏证明权限控制生效然后新增一部电影表格立刻出现新记录接着用关键字模糊搜索确认结果即时变化最后以员工账号下单一部电影订单面板出现记录且库存减一。这套流程覆盖了增、删、改、查、登录、权限、订单、关联查询全部走完大概十分钟。演示前一天把数据库里的样例数据整理干净不要留一堆测试垃圾数据老师最怕看到第一步点开电影列表屏幕上蹦出十几行「测试1」「test2」。6.2 加分扩展导出电影清单与简单的票房统计两个改动小、见效快的扩展方向。第一个是导出用 Apache POI 把电影列表写成 Excel 文件。try (Workbook wb new HSSFWorkbook()) { Sheet sheet wb.createSheet(电影清单); Row header sheet.createRow(0); header.createCell(0).setCellValue(片名); header.createCell(1).setCellValue(导演); header.createCell(2).setCellValue(票价); for (int i 0; i movies.size(); i) { Row row sheet.createRow(i 1); row.createCell(0).setCellValue(movies.get(i).getTitle()); row.createCell(1).setCellValue(movies.get(i).getDirector()); row.createCell(2).setCellValue(movies.get(i).getPrice().doubleValue()); } wb.write(new FileOutputStream(movie_list.xls)); }不想引入 POI 依赖的话用 BufferedWriter 输出 CSV 也一样能交差Excel 打开时识别逗号分隔即可。第二个扩展是统计面板写一条SELECT genre, COUNT(*) FROM t_movie GROUP BY genre的聚合 SQL用 JTable 展示再进一步按订单量算热门电影SELECT m.title, SUM(o.quantity) FROM t_order o JOIN t_movie m ON o.movie_idm.id GROUP BY m.title ORDER BY SUM(o.quantity) DESC。这两条 SQL 是答辩时「系统有什么业务价值」这个问题的最好回答。6.3 交付源码前最后一遍检查我自己交付课设源码前有个固定习惯删掉所有测试输出和临时文件把数据库初始化脚本跑一遍全新环境确认从建库到登录全流程无人工干预再检查一遍 lib 目录确保驱动包在、版本号和代码里的类名对得上最后把 README 里写清楚 JDK 版本、MySQL 版本、启动顺序和默认账号密码。这套源码从「让我跑起来」变成「让老师跑起来」才是真正交付完成。希望这些选型和踩坑记录能帮到你少熬两个通宵。本文还有配套的精品资源点击获取
返回列表