简介这份吉林大学《数据库应用程序开发》实验报告面向正在学习Java与DB2数据库交互的本科生及自学者帮助解决JDBC编程、事务管理与异常处理等实验环节中的典型问题。资源包内含1个doc文档大小约1.76MB完整记录了从补充P171程序片段、修改labstaff.java与labupdate.java到GUI改造、多行插入、空值显示、异常对话框处理等八个任务的调试过程与运行截图。读者可从中获得GRANT授权、TEMPL别名冲突、PreparedStatement参数设置、setAutoCommit事务提交、salary字段溢出与类型转换错误等具体排错思路并理解ResultSet遍历、SQL异常码捕获等核心知识点。目前已有898人学习适合作为数据库应用开发实验的参考范例与查漏补缺材料。1. 吉林大学数据库应用程序开发实验报告从 JDBC 到 GUI 的完整落地路径如果你正在搜“吉林大学数据库应用程序开发实验报告”大概率不是想抄一份报告而是卡在了某个具体环节JDBC 连不上 DB2、Java 写的 GUI 界面按钮点了没反应、增删改查跑通了但事务一并发就出错。这门课的实验本质上是一条完整的链路——用 Java 通过 JDBC 驱动连接 DB2 数据库在 GUI 界面上完成数据的插入、查询、修改和删除。它考验的不是你会不会背 JDBC 那五个步骤而是你能不能把驱动加载、连接管理、SQL 执行、结果集映射和界面事件绑定这几件事串起来跑通。适合正在做数据库课程实验的本科生也适合想用 Java 快速搭一个带界面的数据库小工具的开发者。下面按我实际做这类实验的顺序把每个环节拆开讲。2. 环境搭建与 DB2 连接驱动、URL 和三个必调参数2.1 为什么选 DB2 JDBC 而不是其他组合吉林大学这门实验课指定 DB2 作为后台数据库这不是随便选的。DB2 在事务处理、并发控制和 SQL 标准兼容性上比较严格用它做实验能逼着你把连接关闭、事务提交这些细节处理干净换成某些轻量数据库可能随手写也能跑反而学不到东西。Java 侧用 JDBC 是标准做法因为 JDBC 是 Java 访问关系型数据库的统一接口换数据库只需要换驱动和连接串业务代码基本不动。实际动手前需要确认三样东西DB2 服务已经启动、Java 开发环境配好、DB2 的 JDBC 驱动 jar 包拿到手。DB2 安装目录下一般能找到db2jcc.jar和db2jcc_license_cu.jar这两个是连接 DB2 的核心驱动文件。Java 环境建议用 JDK 8 或 JDK 11这两个版本在课程实验里兼容性最好高版本 JDK 有时会遇到模块化相关的报错。2.2 用 JDBC 连上 DB2 的最小可运行代码先把驱动加载和连接建立跑通这是后面所有操作的基础。下面这段代码是我一般用来验证环境是否正常的模板import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DB2ConnTest { // DB2 连接串jdbc:db2://主机:端口/数据库名 private static final String URL jdbc:db2://localhost:50000/SAMPLE; private static final String USER db2admin; private static final String PASSWORD your_password; public static void main(String[] args) { Connection conn null; try { // 加载 DB2 驱动类这一步不能省 Class.forName(com.ibm.db2.jcc.DB2Driver); // 建立连接设置自动提交为 false 方便后面控制事务 conn DriverManager.getConnection(URL, USER, PASSWORD); conn.setAutoCommit(false); if (conn ! null !conn.isClosed()) { System.out.println(DB2 连接成功); } } catch (ClassNotFoundException e) { System.out.println(驱动类没找到检查 jar 包是否加入 classpath); e.printStackTrace(); } catch (SQLException e) { System.out.println(连接失败错误码 e.getErrorCode()); e.printStackTrace(); } finally { // 连接一定要关否则 DB2 连接数会被占满 if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }这段代码里三个参数最关键。URL 的格式是jdbc:db2://主机:端口/数据库名DB2 默认端口是 50000SAMPLE 是安装时自带的示例库。Class.forName加载的驱动类名是com.ibm.db2.jcc.DB2Driver写错了会直接抛 ClassNotFoundException。setAutoCommit(false)这行在实验里建议加上因为后面做增删改查时你需要手动控制什么时候提交不然一条 SQL 执行完就自动生效出了错没有后悔药。2.3 驱动 jar 包怎么加才不会翻车很多人卡在第一步不是代码写错而是驱动没加载上。如果你用 IntelliJ IDEA正确做法是在项目结构里把db2jcc.jar加到 Libraries 里而不是只放在某个文件夹下。具体路径是 File → Project Structure → Libraries → 加号 → Java然后选中 jar 文件。如果你用命令行编译需要在javac和java命令里都加上-cp参数指向 jar 包路径。注意db2jcc_license_cu.jar也要一起加进去只加db2jcc.jar在某些 DB2 版本上会报 license 相关的错。验证驱动是否加载成功可以在代码里加一行DriverManager.getDrivers()遍历打印看能不能找到 DB2 的驱动实例。如果打印出来是空的说明 classpath 配置有问题跟数据库本身没关系。3. JDBC 增删改查从 SQL 拼接到事务提交的完整实现3.1 用 PreparedStatement 做插入和查询实验报告里最核心的部分就是增删改查。我一般用PreparedStatement而不是Statement原因有两个一是能防 SQL 注入二是参数多的时候不用拼字符串拼到眼花。下面是一个插入用户数据并查询回来的完整示例import java.sql.*; public class UserDao { private static final String URL jdbc:db2://localhost:50000/SAMPLE; private static final String USER db2admin; private static final String PASSWORD your_password; // 插入一条用户记录 public void insertUser(String name, int age, String email) throws SQLException { String sql INSERT INTO USERS (NAME, AGE, EMAIL) VALUES (?, ?, ?); try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt conn.prepareStatement(sql)) { conn.setAutoCommit(false); pstmt.setString(1, name); // 第一个问号对应 NAME pstmt.setInt(2, age); // 第二个问号对应 AGE pstmt.setString(3, email); // 第三个问号对应 EMAIL int rows pstmt.executeUpdate(); conn.commit(); // 手动提交 System.out.println(插入影响行数 rows); } } // 按姓名查询用户 public void queryUserByName(String name) throws SQLException { String sql SELECT ID, NAME, AGE, EMAIL FROM USERS WHERE NAME ?; try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setString(1, name); try (ResultSet rs pstmt.executeQuery()) { while (rs.next()) { System.out.println(rs.getInt(ID) | rs.getString(NAME) | rs.getInt(AGE) | rs.getString(EMAIL)); } } } } }PreparedStatement的setString、setInt这些方法按问号位置从 1 开始编号不是从 0。executeUpdate返回的是受影响的行数插入成功一般返回 1。查询用executeQuery返回ResultSet遍历的时候用rs.next()逐行移动取字段可以用列名也可以用列索引用列名可读性更好但性能略低实验场景下无所谓。3.2 事务控制什么时候 commit什么时候 rollbackDB2 默认是自动提交模式也就是说每条 SQL 执行完就立即生效。但在实验里做批量操作或者有关联的多个操作时你需要把它们放在一个事务里。典型场景是转账A 账户扣钱和 B 账户加钱必须同时成功或同时失败。public void transfer(int fromId, int toId, double amount) { String deductSql UPDATE ACCOUNT SET BALANCE BALANCE - ? WHERE ID ?; String addSql UPDATE ACCOUNT SET BALANCE BALANCE ? WHERE ID ?; Connection conn null; try { conn DriverManager.getConnection(URL, USER, PASSWORD); conn.setAutoCommit(false); // 关闭自动提交开启事务 try (PreparedStatement ps1 conn.prepareStatement(deductSql); PreparedStatement ps2 conn.prepareStatement(addSql)) { ps1.setDouble(1, amount); ps1.setInt(2, fromId); ps1.executeUpdate(); ps2.setDouble(1, amount); ps2.setInt(2, toId); ps2.executeUpdate(); conn.commit(); // 两步都成功才提交 System.out.println(转账成功); } } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); // 任何一步失败就回滚 System.out.println(转账失败已回滚); } catch (SQLException ex) { ex.printStackTrace(); } } } finally { if (conn ! null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }这里的关键点是setAutoCommit(false)之后所有 SQL 都在同一个事务里直到你显式调用commit()才真正写入数据库。如果中间抛异常rollback()会把之前的操作全部撤销。注意rollback要放在 catch 块里而且 rollback 本身也可能抛异常需要再包一层 try-catch。3.3 修改和删除的写法差异修改和删除跟插入的写法几乎一样区别只在 SQL 语句和参数。修改用UPDATE 表名 SET 字段 ? WHERE 条件删除用DELETE FROM 表名 WHERE 条件。有一个容易忽略的点删除和修改一定要带 WHERE 条件不带条件的 DELETE 会清空整张表这在实验里是灾难性的。我一般会在执行前先打印一下 SQL 和参数确认没问题再执行。// 修改用户年龄 public void updateAge(int id, int newAge) throws SQLException { String sql UPDATE USERS SET AGE ? WHERE ID ?; try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, newAge); pstmt.setInt(2, id); int rows pstmt.executeUpdate(); System.out.println(修改影响行数 rows); // 如果返回 0 说明 ID 不存在 } } // 删除用户 public void deleteUser(int id) throws SQLException { String sql DELETE FROM USERS WHERE ID ?; try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt conn.prepareStatement(sql)) { pstmt.setInt(1, id); int rows pstmt.executeUpdate(); System.out.println(删除影响行数 rows); } }executeUpdate的返回值在这里很有用如果修改或删除返回 0说明 WHERE 条件没匹配到任何记录不是代码报错而是数据本身不存在。排查问题时先看这个返回值能省很多时间。4. Java GUI 界面开发用 Swing 把数据库操作挂到按钮上4.1 为什么实验里用 Swing 而不是 JavaFX吉林大学这门实验课通常要求用 Swing 做界面原因是 Swing 是 JDK 自带的不需要额外引入依赖配置简单。JavaFX 从 JDK 11 开始被移出标准库需要单独下载 SDK 并配置模块路径对课程实验来说太重了。Swing 虽然界面风格偏老但做实验够用而且事件监听模型清晰适合理解 GUI 编程的基本套路。一个典型的数据库应用界面至少需要输入框用来填数据、按钮用来触发操作、文本区域用来显示查询结果。布局上我一般用BorderLayout做整体框架中间放JPanel配合GridLayout排列输入框和按钮底部放JTextArea显示结果。4.2 一个能跑通的 Swing JDBC 界面骨架下面这段代码把前面的 UserDao 挂到了按钮事件上点击“插入”按钮会读取输入框内容并写入数据库点击“查询”按钮会把结果打印到文本区域import javax.swing.*; import java.awt.*; import java.sql.*; public class UserFrame extends JFrame { private JTextField nameField new JTextField(10); private JTextField ageField new JTextField(5); private JTextField emailField new JTextField(15); private JTextArea resultArea new JTextArea(10, 40); public UserFrame() { setTitle(用户管理); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new BorderLayout()); // 输入区域 JPanel inputPanel new JPanel(new GridLayout(3, 2, 5, 5)); inputPanel.add(new JLabel(姓名)); inputPanel.add(nameField); inputPanel.add(new JLabel(年龄)); inputPanel.add(ageField); inputPanel.add(new JLabel(邮箱)); inputPanel.add(emailField); // 按钮区域 JPanel buttonPanel new JPanel(new FlowLayout()); JButton insertBtn new JButton(插入); JButton queryBtn new JButton(查询); buttonPanel.add(insertBtn); buttonPanel.add(queryBtn); // 结果区域 resultArea.setEditable(false); JScrollPane scrollPane new JScrollPane(resultArea); add(inputPanel, BorderLayout.NORTH); add(buttonPanel, BorderLayout.CENTER); add(scrollPane, BorderLayout.SOUTH); // 插入按钮事件 insertBtn.addActionListener(e - { String name nameField.getText().trim(); String ageText ageField.getText().trim(); String email emailField.getText().trim(); if (name.isEmpty() || ageText.isEmpty()) { resultArea.append(姓名和年龄不能为空\n); return; } try { int age Integer.parseInt(ageText); UserDao dao new UserDao(); dao.insertUser(name, age, email); resultArea.append(插入成功 name \n); } catch (NumberFormatException ex) { resultArea.append(年龄必须是数字\n); } catch (SQLException ex) { resultArea.append(插入失败 ex.getMessage() \n); } }); // 查询按钮事件 queryBtn.addActionListener(e - { String name nameField.getText().trim(); resultArea.setText(); try { UserDao dao new UserDao(); dao.queryUserByName(name); resultArea.append(查询完成结果见控制台\n); } catch (SQLException ex) { resultArea.append(查询失败 ex.getMessage() \n); } }); pack(); setLocationRelativeTo(null); setVisible(true); } public static void main(String[] args) { // Swing 组件必须在事件分发线程里创建 SwingUtilities.invokeLater(UserFrame::new); } }这段代码里有两个容易出问题的地方。第一SwingUtilities.invokeLater不能省Swing 不是线程安全的所有界面创建和更新都必须在事件分发线程EDT里做直接在 main 方法里 new 窗口在某些系统上会出玄学问题。第二按钮的addActionListener里做了输入校验年龄字段如果填了非数字Integer.parseInt会抛 NumberFormatException这里捕获后提示用户而不是让程序崩溃。4.3 界面事件里做数据库操作的线程问题上面代码里有个隐患数据库操作是在 EDT 线程里执行的。如果查询数据量大或者数据库响应慢界面会卡住不动用户以为程序死了。正确做法是把数据库操作放到后台线程里用SwingWorker来做queryBtn.addActionListener(e - { String name nameField.getText().trim(); resultArea.setText(查询中...); new SwingWorkerString, Void() { Override protected String doInBackground() throws Exception { // 后台线程执行数据库查询 UserDao dao new UserDao(); dao.queryUserByName(name); return 查询完成; } Override protected void done() { try { resultArea.append(get() \n); } catch (Exception ex) { resultArea.append(查询出错 ex.getMessage() \n); } } }.execute(); });doInBackground在后台线程执行done回到 EDT 更新界面。这个模式在实验报告里如果写进去是加分项说明你考虑到了界面响应性。不过如果实验数据量很小直接在 EDT 里做也能跑答辩时被问到再解释就行。5. 避坑与排查DB2 JDBC Swing 实验里最容易翻车的五个点5.1 驱动加载报 ClassNotFoundException现象运行时报java.lang.ClassNotFoundException: com.ibm.db2.jcc.DB2Driver。原因DB2 的 JDBC 驱动 jar 包没有加到项目的 classpath 里或者加的位置不对。IntelliJ IDEA 里常见的情况是 jar 放在了项目目录下但没有在 Project Structure 的 Libraries 里注册。解决确认db2jcc.jar和db2jcc_license_cu.jar都在 Libraries 列表里。命令行运行时用java -cp .;db2jcc.jar;db2jcc_license_cu.jar MainClassWindows或java -cp .:db2jcc.jar:db2jcc_license_cu.jar MainClassLinux/Mac。注意 Windows 用分号Linux 用冒号。5.2 连接被拒绝或超时现象com.ibm.db2.jcc.am.DisconnectNonTransientConnectionException或连接超时。原因DB2 服务没启动或者端口不对或者数据库名写错了。DB2 默认端口是 50000但安装时可能被改过。解决先在命令行执行db2start确认服务启动然后用db2 list db directory查看数据库列表确认库名。端口可以在 DB2 配置里查或者用netstat -an | findstr 50000看端口有没有在监听。5.3 中文乱码现象插入的中文数据在数据库里显示为乱码或者查询出来的中文是问号。原因DB2 数据库的编码和 Java 程序的编码不一致。DB2 默认可能是 UTF-8但 Windows 上某些版本默认用系统编码。解决在连接 URL 后面加上编码参数比如jdbc:db2://localhost:50000/SAMPLE:charsetUTF-8。同时确认 Java 源文件保存为 UTF-8 编码编译时加-encoding UTF-8。5.4 GUI 按钮点了没反应现象界面能显示但点击按钮没有任何反应也不报错。原因事件监听器没有正确绑定或者按钮被其他组件遮挡了。还有一种情况是addActionListener写在了setVisible(true)之后但这种情况在 Swing 里其实也能生效更常见的是监听器里的代码抛了异常但被吞掉了。解决在监听器代码最外层加 try-catch 打印异常堆栈确认有没有异常被静默吞掉。检查addActionListener是否确实绑定到了按钮对象上。用button.getActionListeners().length打印监听器数量确认绑定成功。5.5 事务没提交导致数据“消失”现象代码执行了executeUpdate且返回 1但在数据库客户端里查不到刚插入的数据。原因setAutoCommit(false)之后没有调用commit()数据还在事务里没落盘。程序关闭时连接断开未提交的事务被自动回滚。解决确认每个写操作后面都有commit()。如果用了连接池或框架检查框架是否自动提交。在实验里我一般会在executeUpdate之后立即commit除非是批量操作需要一起提交。6. 进阶技巧用元数据让实验报告多拿十分前面把基本链路跑通了但实验报告如果只写增删改查分数不会太高。我一般会加一个用ResultSetMetaData动态获取列名的功能这样查询结果不需要硬编码列名换一张表也能用。这个技巧在答辩时很能体现你对 JDBC 的理解深度。public void queryWithMeta(String tableName) throws SQLException { String sql SELECT * FROM tableName; try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(sql)) { ResultSetMetaData meta rs.getMetaData(); int columnCount meta.getColumnCount(); // 打印表头 StringBuilder header new StringBuilder(); for (int i 1; i columnCount; i) { header.append(meta.getColumnName(i)).append(\t); } System.out.println(header); // 打印数据行 while (rs.next()) { StringBuilder row new StringBuilder(); for (int i 1; i columnCount; i) { row.append(rs.getString(i)).append(\t); } System.out.println(row); } } }ResultSetMetaData的getColumnCount()返回列数getColumnName(i)返回第 i 列的名字索引从 1 开始。rs.getString(i)不管原始字段是什么类型都能取出来转成字符串做通用查询展示很方便。这个功能加到 GUI 里可以让用户输入表名后自动展示整张表的内容比写死 SQL 灵活得多。另一个值得加的技巧是批量插入。如果实验要求插入大量测试数据逐条executeUpdate会很慢用addBatch和executeBatch能快一个数量级public void batchInsert(ListString[] users) throws SQLException { String sql INSERT INTO USERS (NAME, AGE, EMAIL) VALUES (?, ?, ?); try (Connection conn DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt conn.prepareStatement(sql)) { conn.setAutoCommit(false); for (String[] user : users) { pstmt.setString(1, user[0]); pstmt.setInt(2, Integer.parseInt(user[1])); pstmt.setString(3, user[2]); pstmt.addBatch(); // 加入批次不立即执行 } int[] results pstmt.executeBatch(); // 一次性提交所有 conn.commit(); System.out.println(批量插入完成影响 results.length 行); } }addBatch把参数攒起来executeBatch一次性发给数据库减少了网络往返次数。注意批量操作一定要配合事务否则每条都自动提交就失去批量意义了。executeBatch返回的 int 数组里每个元素对应一条 SQL 的影响行数可以用来校验是否全部成功。这两个技巧我在做实验时都会写进报告里一个体现对 JDBC 元数据接口的掌握一个体现对性能的考虑。答辩时老师问“如果表结构变了你的代码还能用吗”用元数据那套就能直接回答。做这类实验我的习惯是先把最小连接跑通再往上叠功能每加一个功能就单独测一遍不要一口气写完再调不然出了问题根本不知道是哪一层的事。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?