资讯动态

Java+JDBC+Swing商品管理系统实战:从数据库连接到JTable界面

发布时间:2026/10/18 11:58:17 来源:尧图企业网站定制
简介一套面向Java初学者和高校大二学生的商品管理系统完整工程利用JDBC连接MySQL数据库并用Swing构建图形化交互界面适合作为课程设计或Java数据库编程的练手项目。压缩包共18个文件其中10个Java源文件按Controller、Service、DAO、UI分层组织6个XML文件为IDEA工程配置另有.gitignore与.iml辅助文件整体仅16KB结构精简、便于快速上手。已有1953人学习。源码覆盖数据库连接、商品信息增删改查、界面交互等核心模块目录分层清晰可借此理解JDBC驱动加载、SQL执行、结果集处理以及Swing组件布局与事件响应的完整配合方式。对照代码运行后能直观看到Java业务逻辑、数据库操作与图形界面三者如何串联对后续学习SSM、Spring Boot等框架也有铺垫作用。1. 这个 Java JDBC Swing 商品管理系统 zip 里装的是什么快要交作业或者接手一个「商品管理系统」源码包时你大概率会看到一个这样的 zip里面是 Java 源码、数据库脚本和驱动 jar。标题里最核心的三样东西——Java、JDBC、Java Swing 图形界面——分别承担业务逻辑、数据库访问和用户操作窗口三件事。这类系统最适合课程设计、培训后的练手项目也是我从只会写单文件到敢接小工程跨过去的第一道坎数据不写死在内存里而是能持久化到 MySQL界面不打印在控制台而是有输入框和表格。下面把这套系统从零到能跑的方式拆开讲每步给出能复用的代码和参数适合刚学完 Java 语法、想通过完整项目把 JDBC 和 Swing 串起来的人。2. JDBC 连接数据库驱动类、连接串和最小连接工具类的落地用 JDBC 写商品管理系统第一步不是画窗口而是先让 Connection 能稳定地拿到数据库连接。很多 zip 里代码写得完整但换到你的机器上就报ClassNotFoundException或Communications link failure问题几乎都出在驱动版本、连接串参数和驱动 jar 不在 classpath 这三处。先把这些变量钉死再谈 DAO 和 Swing。2.1 MySQL 驱动类与连接串参数5.x 和 8.x 不能混用JDBC 本身只是一套接口真正干活的是数据库厂商提供的驱动实现。商品管理系统在课程设计里最常见的数据库是 MySQL所以驱动 jar 是 mysql-connector-java但 MySQL 服务器大版本不同驱动类和连接写法也有区别。MySQL 5.x 时代的驱动主类是com.mysql.jdbc.Driver到了 MySQL 8.x 的驱动里主类换成了com.mysql.cj.jdbc.Driver。很多翻车现场就是驱动 jar 是 8.x代码里还写着旧类名于是Class.forName直接抛异常。连接串建议写在一行避免折行时带进空格jdbc:mysql://localhost:3306/shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue这段 URL 拆开看就三个关键块第一块localhost:3306/shop是数据库地址和库名第二块useUnicodetruecharacterEncodingutf8保证中文读写不乱码第三块useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue主要给 MySQL 8.x 消警告、定时区。serverTimezone如果不设驱动会拿服务器默认时区本地是东八区但 MySQL 容器用了 UTC查时间字段就会差 8 小时。allowPublicKeyRetrievaltrue只有在 MySQL 8.x 使用 caching_sha2_password 认证时才会用到遇到Public Key Retrieval is not allowed错误时把它加上即可。下表是这几个参数的实际作用以后改连接串对着改就行参数作用常见取值useUnicode / characterEncoding指定客户端字符集true utf8 或 UTF-8useSSL本地开发通常不需要加密连接falseserverTimezone指定时区影响 DATETIME 读写Asia/ShanghaiallowPublicKeyRetrieval允许客户端获取服务器公钥trueconnectTimeout连接超时毫秒5000如果连接串里不加useSSLfalseMySQL 8 驱动启动时会打印一大段 SSL 警告虽然不致命但很容易让新手误以为是报错。而connectTimeout这种参数一般不会写在小系统里一旦数据库服务不在同一台机器加上它至少能让失败在 5 秒内出现而不是傻等两分钟。2.2 用 db.properties 管理连接参数别把密码写死在代码里我见过不少商品管理系统的源码在 DBUtil 里直接写DriverManager.getConnection(jdbc:mysql://..., root, 123456)这样自己跑通很快但换一台机器就要改代码重新编译交作业时导师换数据库密码你还得重新打包。常见做法是把连接参数放到db.properties程序启动时读取改配置不用动 Java 代码。在src/main/resources下新建db.propertiesdrivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/shop?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue usernameroot password123456对应写一个DBUtil连接工具类把所有数据库连接逻辑收敛在一个类里public class DBUtil { private static String url; private static String username; private static String password; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { if (in null) { throw new RuntimeException(db.properties 不存在请检查 resources 目录); } Properties props new Properties(); props.load(in); url props.getProperty(url); username props.getProperty(username); password props.getProperty(password); Class.forName(props.getProperty(driver)); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } public static void close(AutoCloseable... resources) { for (AutoCloseable r : resources) { if (r ! null) { try { r.close(); } catch (Exception ignored) { // close 失败不需要打断主流程 } } } } }getResourceAsStream用的是 classpath 路径所以 jar 包内也能读到不会出现本地能跑、打包后找不到配置的情况。Class.forName在 JDBC 4.0 之后其实可以省略驱动会自动注册但显式写出来有一个好处驱动 jar 没放对时能在加载阶段立刻报错而不是到获取连接时才报一个含糊的No suitable driver found。close(AutoCloseable...)是偷懒写法传多个资源进来一起关比在每个 DAO 里写三层 try-catch 干净。把driver也放进配置而不是写死在代码里是为了以后换数据库做准备。比如从 MySQL 切到 MariaDB驱动类名不同但连接串结构可能接近改一行配置就能试。虽然这个系统标题是 MySQL但多留一个口子没有坏处。2.3 最小连接验证先跑通 SELECT 1再写任何界面写完DBUtil不要急着写 DAO先写一个最小验证类确认连接串、账号、驱动都正确。这一步能省下后面排查界面问题的几小时。public class ConnectionCheck { public static void main(String[] args) { try (Connection conn DBUtil.getConnection(); Statement st conn.createStatement(); ResultSet rs st.executeQuery(SELECT 1)) { if (rs.next()) { System.out.println(连接成功SELECT 1 rs.getInt(1)); } } catch (SQLException e) { e.printStackTrace(); } } }这段代码用 try-with-resources 一次关闭三个资源SQLException打印完整堆栈。如果看到Communications link failure说明 MySQL 服务没启动或端口不对如果看到Access denied for user检查用户名密码和该账号是否有远程访问权限如果是时区相关报错回 2.1 加serverTimezone。输出连接成功SELECT 1 1后后面所有 DAO 方法才有继续往下写的意义。这一节是后续所有 CRUD 的地基。我在带 A 同学做类似系统时他跳过这一步直接写了 300 行 DAO最后发现是驱动版本问题白排查了两个小时。所以建议你也按这个顺序走先连接再建表再写界面。3. 商品管理系统的表结构和 JDBC CRUD 封装数据库表结构会直接决定 DAO 写起来顺不顺手。商品管理系统最核心的通常是「分类」和「商品」两张表。如果源码包里只有一张商品表也建议补上分类表否则商品上的分类字段只能写死字符串后期改一个分类名就要全表 UPDATE非常难受。3.1 建表语句与字段选型为什么价格用 DECIMAL 而不是 double商品表的字段一般包括 id、分类、名称、价格、库存、创建时间。价格这种字段如果图省事用 double结算时会出现 0.10.2 不等于 0.3 的浮点误差正确做法是用DECIMAL(10,2)由数据库把小数精度定死Java 侧再用BigDecimal接收。销毁分类、删除商品这类操作在课程设计里不多但外键约束值得加上至少能证明你对关系型数据库有基本认知。CREATE DATABASE shop DEFAULT CHARACTER SET utf8mb4; USE shop; CREATE TABLE category ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, sort INT NOT NULL DEFAULT 0 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE product ( id BIGINT AUTO_INCREMENT PRIMARY KEY, category_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES category(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;id 用BIGINT AUTO_INCREMENT而不是INT因为商品数量增长空间要大一些而且主键用 BIGINT 在很多框架里是默认习惯。price用DECIMAL(10,2)表示最多 8 位整数加 2 位小数对普通商品的价位足够。create_time用数据库默认时间Java 端不用手动赋当前时间减少一步出错的可能。外键fk_product_category会保证插入商品时category_id必须存在这是一个合理约束代价是删除分类前要先处理掉该分类下的商品。3.2 实体类和 DAO 接口把数据库表和 Java 对象对齐JDBC 操作最终要落到 Java 对象上。先写一个Product实体类字段和product表一一对应价格用BigDecimal库存用Integer。这里省略 getter/setter实际代码里要补齐。public class Product { private Long id; private Long categoryId; private String categoryName; private String name; private BigDecimal price; private Integer stock; private LocalDateTime createTime; // getter / setter 省略 }然后定义一个ProductDao接口把增删改查方法固定下来后面换实现类不用动界面代码public interface ProductDao { ListProduct findAll(); ListProduct findByKeyword(String keyword); Product findById(Long id); boolean insert(Product product); boolean update(Product product); boolean deleteById(Long id); }接口的好处是 Swing 界面只依赖ProductDao现在用 JDBC 实现以后想改成 MyBatis 或 Spring JDBC只需换一个实现类界面层几乎不用动。对课程设计来说这不算过度设计它能让代码结构一眼看明白答辩时也更好讲。3.3 用 PreparedStatement 完成 CRUD关键方法与占位符顺序实现ProductDaoImpl时所有 SQL 都使用PreparedStatement不要用Statement直接拼字符串。用Statement拼name字段时如果商品名里有一个英文单引号SQL 就会断掉更严重的是用户输入 or 11这种内容可以直接改变查询逻辑这就是最基础的 SQL 注入。PreparedStatement 先用?占位再用setXxx传值驱动会转义特殊字符既安全又省心。查询所有商品并映射成对象的代码Override public ListProduct findAll() { ListProduct list new ArrayList(); String sql SELECT p.id, p.name, p.price, p.stock, p.category_id, c.name AS category_name FROM product p LEFT JOIN category c ON p.category_id c.id ORDER BY p.id; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { Product p new Product(); p.setId(rs.getLong(id)); p.setName(rs.getString(name)); p.setPrice(rs.getBigDecimal(price)); p.setStock(rs.getInt(stock)); p.setCategoryId(rs.getLong(category_id)); p.setCategoryName(rs.getString(category_name)); list.add(p); } } catch (SQLException e) { throw new RuntimeException(查询商品列表失败, e); } return list; }这里用了LEFT JOIN而不是INNER JOIN原因是如果某条商品的分类被删了尽管外键阻止但应用层可能绕过LEFT JOIN 仍然能查出来分类名显示为 null。rs.getBigDecimal对应数据库的DECIMALrs.getLong对应BIGINT类型匹配既减少转换又避免拆箱异常。新增商品时需要拿到数据库生成的自增 id不能只用executeUpdate后就不管。常见的写法是创建 PreparedStatement 时传入Statement.RETURN_GENERATED_KEYS执行后再从getGeneratedKeys()里读 idOverride public boolean insert(Product product) { String sql INSERT INTO product(category_id, name, price, stock) VALUES (?, ?, ?, ?); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { ps.setLong(1, product.getCategoryId()); ps.setString(2, product.getName()); ps.setBigDecimal(3, product.getPrice()); ps.setInt(4, product.getStock()); int rows ps.executeUpdate(); if (rows 0) { return false; } try (ResultSet keys ps.getGeneratedKeys()) { if (keys.next()) { product.setId(keys.getLong(1)); } } return true; } catch (SQLException e) { throw new RuntimeException(新增商品失败, e); } }占位符从 1 开始计数setLong(1, ...)对应 SQL 里的第一个?。RETURN_GENERATED_KEYS在 MySQL 驱动里是支持的但要注意如果插入时没有指定AUTO_INCREMENT字段需要先executeUpdate再读取。若rows 0说明没有任何行受影响直接返回 false避免生成键落空。3.4 模糊搜索、更新与删除给 Swing 界面补全最后两个动作商品管理系统的搜索框一般会按名称模糊匹配。SQL 用LIKE ?Java 侧拼好%关键词%再传进去Override public ListProduct findByKeyword(String keyword) { String sql SELECT p.id, p.name, p.price, p.stock, p.category_id, c.name AS category_name FROM product p LEFT JOIN category c ON p.category_id c.id WHERE p.name LIKE ? ORDER BY p.id; ListProduct 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()) { // 与 findAll 相同的映射逻辑省略 list.add(mapRow(rs)); } } } catch (SQLException e) { throw new RuntimeException(搜索商品失败, e); } return list; }更新和删除的思路类似UPDATE product SET category_id?, name?, price?, stock? WHERE id?按顺序 set 五个参数DELETE FROM product WHERE id?只需 set 一个参数。这里特别强调的是executeUpdate的返回值更新时如果内容和原来完全一样MySQL 可能返回 0此时不能简单把返回 0 当作失败删除时返回 0 说明 id 不存在可以提示用户。很多新手把rows 0当作成功遇到返回 0 就误报「操作失败」这是常见误解。DAO 层写完后可以先用一个简单的 main 方法测试插入两条商品、搜索、更新价格、删除确认数据库里结果正确再进入 Swing 界面开发。这样把逻辑错误拦在界面之前界面只负责调 DAO排错面会小很多。4. Swing 图形界面JTable 渲染、商品表单和事件监听商品管理系统的 Swing 界面通常是三个部分顶部按钮区、中间 JTable 表格、底部状态栏。顶部放「搜索、新增、编辑、删除、刷新」按钮中间表格展示商品 id、名称、价格、库存和分类。Swing 本身只负责把ProductDao拿到的数据展示出来并收集用户输入核心逻辑还是在 DAO 里。4.1 主窗口与 JTable用 DefaultTableModel 还是 AbstractTableModel主窗口用一个JFrame中央区域放JTable。新手喜欢直接table new JTable(data, columns)但这样后续刷新时要重新创建表格对象绑定到JScrollPane又要改引用。更稳的写法是每次刷新时构建一个新的DefaultTableModel调用table.setModel(model)让 JTable 重新加载模型。这个方式对商品数量不大几千行以内的系统足够。private void refreshTable(ListProduct products) { String[] columns {ID, 商品名, 价格, 库存, 分类}; Object[][] data new Object[products.size()][columns.length]; for (int i 0; i products.size(); i) { Product p products.get(i); data[i][0] p.getId(); data[i][1] p.getName(); data[i][2] p.getPrice(); data[i][3] p.getStock(); data[i][4] p.getCategoryName(); } DefaultTableModel model new DefaultTableModel(data, columns) { Override public boolean isCellEditable(int row, int column) { return false; } }; table.setModel(model); }DefaultTableModel内部用 Vector 存储虽然被很多文章吐槽老旧但它和 JTable 配合最省事。重写isCellEditable返回 false防止用户双击单元格把程序数据改乱如果需要双击编辑可以在弹窗里做而不是直接编辑表格。分类列显示的是categoryName来自 DAO 里的 LEFT JOIN 结果这样表格不需要再到分类表查一次。4.2 新增/编辑弹窗JDialog JFormattedTextField JSpinner新增和编辑共用一个ProductDialog弹窗类传入Product对象区分两种模式传 null 表示新增传非 null 表示编辑并回填数据。布局用最简单的GridBagLayout或两行一行一个组件的JPanel保证字段对齐。价格输入用JFormattedTextField库存用JSpinner分类用JComboBox。public class ProductDialog extends JDialog { private JTextField nameField new JTextField(20); private JFormattedTextField priceField new JFormattedTextField(new DecimalFormat(0.00)); private JSpinner stockSpinner new JSpinner(new SpinnerNumberModel(0, 0, 999999, 1)); private JComboBoxString categoryBox new JComboBox(); private MapString, Long categoryIdMap new HashMap(); private Product product; private boolean saved false; public ProductDialog(Frame owner, Product product) { super(owner, product null ? 新增商品 : 编辑商品, true); this.product product; loadCategories(); if (product ! null) { nameField.setText(product.getName()); priceField.setValue(product.getPrice()); stockSpinner.setValue(product.getStock()); categoryBox.setSelectedItem(product.getCategoryName()); } // 布局代码省略保存按钮事件调用 save() } private void save() { Product p product null ? new Product() : product; p.setName(nameField.getText().trim()); p.setPrice(new BigDecimal(priceField.getValue().toString())); p.setStock((Integer) stockSpinner.getValue()); p.setCategoryId(categoryIdMap.get(categoryBox.getSelectedItem())); ProductDao dao new ProductDaoImpl(); boolean ok (product null) ? dao.insert(p) : dao.update(p); if (ok) { saved true; dispose(); } else { JOptionPane.showMessageDialog(this, 保存失败); } } }categoryIdMap的 key 是界面显示的分类名value 是数据库里的分类 id。保存时不能从categoryBox.getSelectedItem()直接拿 id那只是给用户看的名称。JFormattedTextField拿到的是字符串保存前用DecimalFormat格式化校验避免用户输入1,000或-5这种数据直接进数据库。SpinnerNumberModel(0, 0, 999999, 1)的第一个 0 是当前值第二个 0 是最小值999999 是最大值1 是步长这样库存不会出现负数。4.3 事件监听与 EDT为什么查询一百条记录界面会卡住按钮事件监听都很简单新增、编辑、删除、搜索分别对应ProductDialog、删除确认和findByKeyword。但要提醒一句Swing 界面运行在事件调度线程EDT上如果直接在按钮回调里执行dao.findAll()数据库查询期间的界面会变成「假死」窗口拖不动按钮点没反应。商品表数据少时感觉不明显一旦数据到几万行这个体验会非常糟糕。常见的改进是引入SwingWorker让耗时操作在后台线程执行完成后回 EDT 更新界面。这里以查询列表为例private void refreshTableAsync() { new SwingWorkerListProduct, Void() { Override protected ListProduct doInBackground() { return productDao.findAll(); } Override protected void done() { try { refreshTable(get()); } catch (Exception e) { JOptionPane.showMessageDialog(MainFrame.this, 加载商品列表失败 e.getMessage()); } } }.execute(); }doInBackground是后台线程不能操作界面组件done自动切回 EDT再把查询结果塞给表格。如果商品数据量特别大还能在doInBackground里调用publish分批传数据但商品管理系统一般用不到。对于课程设计如果不想引入并发复杂度同步查询也能接受只要记住这个卡顿边界。编辑功能要注意先判断用户选中了哪一行table.getSelectedRow()返回 -1 表示没选中这时要弹提示而不是进入编辑弹窗。删除按钮同理最好在删除前弹一个确认框避免手滑把数据删了没法恢复。这些细节虽然不产生功能但直接影响使用体验答辩时也是加分项。5. 商品管理系统落地避坑五个让新手翻车的地方如果说前四章是搭积木那这章就是提前告诉你积木哪里容易塌。以下五个坑是我在带别人做类似系统时反复见的几乎每一个都能消耗掉半天以上。5.1 驱动类找不到或「No suitable driver found」现象运行程序时Class.forName抛ClassNotFoundException: com.mysql.cj.jdbc.Driver或者第一次getConnection抛SQLException: No suitable driver found for jdbc:mysql://...。原因驱动 jar 没有加入项目 classpath或者驱动类和 MySQL 服务器版本不匹配。比如代码里写着 8.x 的新驱动类名但lib下面放的是 5.x 的旧驱动包反过来旧版驱动包遇到 MySQL 8 也会在认证阶段报错。解决先确认 MySQL 服务器大版本再确认 mysql-connector-java 的 jar 版本。驱动 jar 放到项目里并加入依赖IDE 里可以在项目结构中给模块添加 jar用命令行编译记得javac -cp mysql-connector-java.jar。通用做法是让驱动类名和 jar 版本一致5.x 用com.mysql.jdbc.Driver8.x 用com.mysql.cj.jdbc.Driver。5.2 MySQL 8 认证插件报错Public Key Retrieval is not allowed现象连接 MySQL 8.x 时getConnection直接抛Public Key Retrieval is not allowed换一台电脑又可能正常属于典型的「玄学问题」。其实不玄是 MySQL 8 默认认证插件caching_sha2_password在非 SSL 连接下需要先从服务器获取公钥驱动默认不允许这个行为。原因连接串缺少allowPublicKeyRetrievaltrue。本地开发用 root 账号连接时最常见应用服务器连接也常见。解决在 JDBC URL 最后加allowPublicKeyRetrievaltrueuseSSLfalse。需要注意的是这个参数和 SSL 是配合的如果useSSLtrue而不配置证书公钥获取逻辑又会变。本地开发直接两者一起加生产环境用useSSLtrue并配置证书不要照搬课后作业的写法。5.3 中文乱码连接串、建表字符集、Java 编译编码三处同时查现象界面表格里商品名显示为???或者从界面插入数据库后在数据库客户端里看是乱码但在界面里看又是正常的也有反过来数据库正常但 Swing 显示乱码。原因字符集问题有一个链路任何一处不一致都会出问题。连接串没写characterEncodingutf8驱动会用默认编码数据库表是latin1中文自然存不下Java 源码文件用 GBK 编译字符串在内存里已经是错的。解决三层都统一成 UTF-8。连接串加characterEncodingutf8建表用DEFAULT CHARSETutf8mb4IDE 编译选项加-encoding UTF-8。如果已经插入乱码就要先清理数据再改字符集。Swing 界面的字体如果显示方框通常是系统字体不支持中文换掉JTable字体即可和数据库无关。5.4 连接泄漏Too many connections 把整个系统拖垮现象系统运行十几分钟后点任何按钮都没反应数据库日志或报错信息出现Too many connections。重启后又正常过一阵子又不行。原因DAO 里获取了 Connection但没有在 finally 里关闭或者只关闭了 ResultSet 而丢掉了 Connection。每个查询都占用一个连接MySQL 默认最大连接数有限连接被耗尽后所有新请求只能排队界面看起来就是卡死。解决所有数据库操作都用 try-with-resources把Connection、PreparedStatement、ResultSet放在一个 try 块里关闭。如果项目里已有大量手写的conn.close()检查是否每个 return 分支都能走到。也可以用DBUtil.close(rs, ps, conn)兜底。要快速定位用SHOW PROCESSLIST看哪些连接长时间Sleep基本就是泄漏点。5.5 批量删除只删了一半事务没提交导致状态不一致现象在界面里选中多个商品删除程序循环执行DELETE删到第三条报外键错误或 SQL 异常前两条已经没了后两条还在界面上数据只剩一半用户不知道到底是成功还是失败。原因默认情况下 MySQL 的autocommit1每一条DELETE都是独立事务前两条成功已经被提交第三条失败不会回滚前面的操作。对商品管理系统来说这比删不掉更危险因为数据库状态已经不可信。解决把批量删除包在一个事务里要么全成功要么全失败。关键代码是conn.setAutoCommit(false)循环删除后conn.commit()异常时conn.rollback()try (Connection conn DBUtil.getConnection()) { conn.setAutoCommit(false); try (PreparedStatement ps conn.prepareStatement( DELETE FROM product WHERE id ?)) { for (Long id : ids) { ps.setLong(1, id); ps.addBatch(); } ps.executeBatch(); conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } }用addBatch而不是一次次executeUpdate网络往返次数也少性能更好。事务开启后Connection必须手动 commit 和 rollback这个 Connection 就不能再用 try-with-resources 直接关但关闭连接本身仍会自动回滚未提交事务所以要注意顺序。批量删除完成后界面上要重新查一遍列表不要只删掉表格选中的行因为数据库可能因外键等情况有变化。6. 发布前最后一步打包成可运行 Jar 与两张验证清单商品管理系统在本地跑通只是第一步交付给同学、导师或同事时对方机器上未必有你的 MySQL 账号。我的习惯是最后做两件事把程序打包成可运行 Jar再按一张冒烟测试清单手动过一遍功能。打包时驱动 jar 必须跟着一起进去。常见做法是在开发工具里选「Jarfrom modules with dependencies」入口类选MainFrame依赖库打成一个含 lib 的可运行包。打包后至少看一眼 jar 大小如果只有几十 KB多半是没把驱动打进去。比动手打包更先做的是把db.properties放到 Jar 外部运行时先读config/db.properties没有再回退到 classpath对方只需改密码不用重打包。验证清单我一般就三行启动后能看到商品列表新增一个商品后表格多一行且数据库多一条记录编辑价格后重启程序数据不丢。这三条都过这个系统就具备了最基本的商品管理能力。剩下的搜索、删除、分类不过是这三个动作的组合。我之前给某小组做模拟项目X时就吃过连接串写死在代码里的亏换台电脑演示前五分钟才发现连不上库后来把所有配置都搬到 Jar 外并附带一份 README这个问题再没出现过。一个不起眼的配置外部化习惯能省掉很多现场救火的时间。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑