资讯详情

从DB2实验报告看JDBC编程:连接、事务与GUI的完整链路

📅 2026/10/24 18:07:35 | 华诺云谱 👁 阅读
从DB2实验报告看JDBC编程:连接、事务与GUI的完整链路
简介吉林大学《数据库应用程序开发》实验报告覆盖 JDBC 连接、SQL 查询执行、ResultSet 遍历、PreparedStatement 参数绑定、事务自动提交控制及 SQL 异常处理等核心内容适合正在学习 Java 与 DB2 数据库交互的高校学生或自学者对照参考。报告基于真实实验调试过程整理包含权限授予、对象命名、溢出分析与事务提交开关等典型排错记录能帮助读者快速定位同类问题。资源为 1 个 doc 文档整体仅 1.76MB便于下载阅读已有 901 人浏览学习。实验任务由浅入深从补充程序片段到 GUI 化改造再到插入、修改与异常对话框处理完整呈现数据库应用程序开发从基础到综合的实践路径可作为课程设计或实验报告的思路借鉴。1. 从一份 DB2 实验报告看 JDBC 编程连接、事务与 GUI 的完整链路初次接触 JDBC 的人往往把第一条Connection写通就算过关但实际开发里最容易翻车的恰恰是连接前后的权限、命名空间和事务提交。这份数据库应用程序开发实验报告把一条完整链路摊开了从给用户授权、连上 SAMPLE 库到用 Statement 跑查询、用 PreparedStatement 做参数化更新再到把程序改造成 JOptionPane 弹窗交互最后把所有异常按违反约束、类型不匹配、溢出、非法符号分门别类弹出来。中间还记录了一个特别有代表性的排错过程——同样的更新语句输入 15 时溢出、16 时更新 0 行、20 时执行成功但库里没变化三个现象对应三个完全不同的原因。对正在入门 JDBC 的初学者以及想把课程作业改成能用的数据库工具的人来说这份报告值得照着敲一遍也值得把踩过的坑提前避开。2. JDBC 基础链路权限、连库、查询与结果集的三层设计2.1 权限前置先解决谁能访问哪张表再谈连库数据库应用开发的第一个坑往往不是代码本身而是代码连上库之后发现没权限。报告里第一条报错就是 db2admin 没有访问 EMPLOYEE 表的权限解决办法是让表的属主用户执行授权。在实验环境里表的属主通常是一个独立的模式用户这里记为 USR01对应的授权语句是-- 把 EMPLOYEE 表的查询权限授给 db2admin GRANT SELECT ON EMPLOYEE TO db2admin; -- 如果后面要做插入和更新一并把 INSERT、UPDATE 授过去 GRANT INSERT, UPDATE ON EMPLOYEE TO db2admin;这里有个容易忽略的细节db2admin 是数据库实例的管理账号但管理实例不等于能读业务表。表权限由表属主控制所以必须先以 USR01 登录数据库执行 GRANT再回到 Java 程序里用 db2admin 连库。这条权限门槛不走通后续所有查询和更新都会以无权限执行操作类的 SQL 错误返回。环境里如果有多张表要操作建议把授权语句集中放在建表脚本后面每次重置环境时一起跑避免一边写代码一边回数据库补授权。权限粒度上实验场景给到表级就够了不必用到ALL PRIVILEGES如果账号只需要查询给 SELECT 就够多余的 INSERT 权限反而留下隐患。2.2 DriverManager.getConnectionURL、用户名与密码的三参数约定连接代码是每次实验都要重写一遍的部分报告里任务 2 和任务 3 反复出现同一套写法核心就三行// 加载 DB2 JDBC 驱动类名以你环境里实际的 jar 包为准 Class.forName(COM.ibm.db2.jdbc.app.DB2Driver); // 连接 SAMPLE 数据库URL 格式固定为 JDBC:db2:数据库名 Connection sample DriverManager.getConnection(JDBC:db2:sample, db2admin, db2admin);第一个参数是 JDBC URL实验里是JDBC:db2:sample前面是协议和子协议后面跟数据库名。第二个参数是账号第三个是口令。URL 还有一种常见写法是在库名后追加参数比如指定连接默认模式String url JDBC:db2:sample:currentSchemaUSR01;;这个写法能直接解决SQL 里不带模式前缀导致找不到表的问题因为连接建立后未加前缀的表名都会尝试在 USR01 模式下解析。动手前先确认你的驱动版本支持这种参数格式不同版本的 URL 参数分隔符不完全一致。容易踩坑的点在Class.forName。JDBC 4 之后的驱动包可以通过 SPI 机制自动注册不写这行也能连上但如果 CLASSPATH 里的驱动 jar 没放对报的却是ClassNotFoundException那就要先检查 classpath 里的驱动包。驱动类名在不同环境也有差异实验环境里的 Type 2 驱动是一个名字新版 db2jcc 驱动又是另一个名字以你实际加载到的 jar 包内容为准。Connection 是重量级资源创建后要记得关闭。实验里的程序一次性跑完无所谓但如果你把这套代码改造成 Web 服务或工具类连接不关很快就会把数据库连接池拖垮。我一般会写成 try-with-resources 或者手动在 finally 里 close。2.3 Statement 与 ResultSet从 executeQuery 到 next() 的遍历节奏任务 2 的 labstaff.java 展示的就是一条完整的查询链创建 Statement、执行 SQL、拿到 ResultSet、遍历结果集。去掉报告里的填空痕迹后核心逻辑是这样的Statement stmt sample.createStatement(); // 查询语句里表名带模式前缀避免不同用户下同名表造成混淆 ResultSet rs stmt.executeQuery( select NAME, JOB, SALARY from USR01.staff where ID 10); boolean more rs.next(); if (more) { // 用列名取值比用列索引更不容易出错 System.out.println(rs.getString(NAME) , rs.getString(JOB)); } more rs.next(); // 再次调用判断是否还有下一行Statement 适合执行固定的静态 SQLexecuteQuery专门跑 SELECT返回值是 ResultSet。ResultSet 的next()方法把游标往下移一行返回 true 表示当前行有数据false 表示结果集已经遍历完。报告里第一次next()是为了拿到第一行数据第二次next()是为了判断是否还有第二行这个模式等价于while (rs.next()) { ... }的循环骨架。两个高频问题一是把next()的结果直接丢给变量却没意识到每次调用都会移动游标连续调用两次会跳行二是结果集为空时第一行next()就返回 false如果没做判断就直接getString会抛出结果集已耗尽之类的运行时异常。取值方式上getString(NAME)按列名读getString(1)按索引读列名在 SQL 里用了别名时要写别名而不是原始列名。这里零散的小规则单独记容易忘写一个查询工具类时一次性把它们固化下来后面复用就很顺手。3. 更新与事务PreparedStatement、setAutoCommit 与溢出排查3.1 PreparedStatement 参数化更新setInt 与 executeUpdate 的配合任务 3 的核心是把固定 SQL 改成带参数的形式让输入不同的部门号变成程序运行时的变量。PreparedStatement 比起字符串拼接有两个明显收益SQL 预编译一次多次执行效率更高参数化写法天然规避了字符串拼接带来的类型转换和注入问题。import java.sql.*; Connection sample DriverManager.getConnection(JDBC:db2:sample, db2admin, db2admin); // 关闭自动提交事务边界交给程序控制 sample.setAutoCommit(false); String sqlstmt update staff set salary salary * 2 where dept ?; PreparedStatement pstmt sample.prepareStatement(sqlstmt); pstmt.setInt(1, mydeptno); // 占位符从 1 开始编号 int updateCount pstmt.executeUpdate(); System.out.println(updateCount); sample.commit(); // 显式提交这次事务 pstmt.close(); sample.close();setInt(1, mydeptno)的第一个参数是占位符的位置第二个参数是要绑定的值。除了 setInt还有 setString、setDouble、setDate 等对应不同列类型。executeUpdate返回受影响的行数比如按 dept 更新 3 行就返回 3返回 0 表示没有匹配到的记录——任务 3 里输入 16 时反馈修改了 0 行就是因为 dept 为 16 的记录在 staff 表里并不存在。这里别把返回值和更新后的数据量混为一谈。PreparedStatement 可以复用循环里反复setInt、executeUpdate即可不必每次新建语句。但要注意同一实例上一轮设置的值不会自动清除如果某一次少设了一个参数可能沿用旧值。养成习惯每次执行前把所有占位符重新设置一遍。3.2 关掉自动提交之后为什么更新成功数据却没变这条是报告里最有价值的排错过程之一。现象是输入 20 或 30 时程序反馈执行成功回数据库里查staff 表数据完全没变。用命令行工具直接执行同样的 update 又是成功的说明数据库层面允许修改问题出在 Java 程序里。定位到最后就是这一行的开关sample.setAutoCommit(false);关闭自动提交后如果没有显式调用commit()事务会一直停留在未提交状态。程序结束或连接关闭时未提交的事务被数据库回滚所以看到的结果就是执行成功但数据没变。报告的解决办法是直接把 false 改成 true让每条语句执行后立即提交更规范的做法是保留 false在executeUpdate之后手动调用sample.commit()失败时调用sample.rollback()这样既保留了事务边界又让修改真正生效。实操里有一个快速验证法在同一个连接里 update 之后立刻 select能看到修改后的值换个新连接再查发现还是旧值。这就是同一事务内可见但事务尚未提交的典型特征。遇到执行成功但是没生效的怪问题时第一个检查点永远是这个开关。3.3 溢出定位salary2 与 salary1.01 的分支判断法任务 3 里还有一个典型的数值溢出问题。输入 15 时执行更新报类型转换溢出。报告里用了一个很聪明的分支验证法把更新语句里salary salary ?改成salary salary * 2输入 15 和 20 都溢出再改成salary salary * 1.01两个都不溢出。由此证明溢出原因就是更新后的数值超出了列的精度范围而不是 SQL 语法或驱动问题。-- 用最小化查询确认问题行的当前值与列属性 select id, dept, salary from USR01.staff where id 20; -- 查看列定义确认 salary 的类型与精度 describe table USR01.staff;describe的输出里能看到 salary 的具体类型和精度。假如列定义为 DECIMAL(7,2)最大只能表示 99999.99乘 2 之后自然就超范围了。这个定位思路可以沉淀成三个固定问题是不是所有输入都溢出如果只有部分输入溢出那多半是数值大小差异不是驱动问题。换一个更保守的运算能不能通过能说明问题在数值范围。直接查一下目标行的当前值看看是接近上限还是中间某档这一步通常能直接确认根因。之所以说这个案例经典是因为报错信息很有迷惑性初看会以为是 SQL 写错了实际上数据都没问题纯粹是数值越界。数据库这边报的类型转换溢出其实已经把答案写在错误信息里了只是很多人第一反应是改 SQL 结构而不是去查列精度。4. GUI 化改造JOptionPane、插入面板与异常对话框的交互设计4.1 用 JOptionPane 接住输入和输出任务 4 把 labupdate.java 改造成 GUI 形式用户输入和结果输出都通过 JOptionPane 弹窗完成。核心不是界面复杂度而是把原来的控制台打印 Scanner 读入替换成弹窗输入 弹窗输出String input JOptionPane.showInputDialog(请输入部门号); if (input null) { // 用户点了取消或关闭返回 null return; } int mydeptno Integer.parseInt(input); String sqlstmt update staff set salary salary * 2 where dept ?; PreparedStatement pstmt sample.prepareStatement(sqlstmt); pstmt.setInt(1, mydeptno); int n pstmt.executeUpdate(); JOptionPane.showMessageDialog(null, 更新了 n 行);showInputDialog返回的永远是 String第一步必须做类型转换。Integer.parseInt在用户输入非数字时会抛NumberFormatException这在任务 7 的异常分类里会被归到类型不匹配。showMessageDialog的第一个参数是父窗口传 null 表示居中弹窗。这里有个小习惯凡是用户输入型弹窗先判空再转换否则用户点一下取消程序就炸了。GUI 化的意义在于把程序从命令行专用变成别人也能用但输入校验逻辑一点都不能省。4.2 插入操作的三种形态单行、多行、子查询任务 5 要求向 TEMPL 表插入行并且要同时支持单行插入、多行插入和通过子查询插入。三者走同一个executeUpdate区别在 SQL 和参数的准备方式// 单行插入固定的两条参数 String single insert into USR01.TEMPL values (?, ?); PreparedStatement ps1 sample.prepareStatement(single); ps1.setInt(1, 101); ps1.setString(2, 第一批); ps1.executeUpdate(); // 多行插入循环里换参数反复执行或用 addBatch 批量提交 for (int i 0; i rows.length; i) { ps1.setInt(1, rows[i][0]); ps1.setString(2, rows[i][1]); ps1.executeUpdate(); } // 子查询插入值来自另一张表的查询结果 String sub insert into USR01.TEMPL select id, dept from USR01.staff where dept ?; PreparedStatement ps2 sample.prepareStatement(sub); ps2.setInt(1, 20); ps2.executeUpdate();单行插入的参数个数和表的列数一致多行插入复用同一个 PreparedStatement循环里换参数即可数据量大时可以改成addBatch()加executeBatch()减少网络往返子查询插入的语法是insert into 目标表 select ... from 源表参数只对应子查询里的条件。这里常见的困惑是把参数个数必须等于表列数当成铁律实际上 PreparedStatement 的参数个数完全由 SQL 里的占位符决定子查询插入可能只有一个条件参数但插入的却是整段查询结果。任务 5 里还要求把结果集中的空值显示为空字样。最直接的写法是取完值之后立刻判断String val rs.getString(name); if (rs.wasNull()) { // 上一列取出来的值是否为 NULL val 空; }wasNull()必须紧跟取值方法之后调用中间隔了其他取值操作判断的就是别的列了。更省事的方案是rs.getObject(...)拿对象再判断是否为空但显示端统一转成字符串时注意别把数据库的 NULL 和空字符串混为一谈。4.3 异常按类提示违反约束、类型不匹配、溢出与非法符号任务 7 要求把异常处理做成对话框提示并且要区分违反约束、类型不匹配、溢出、SQL 语句里遇到非法符号这四类。关键在 catch 块里从异常对象身上把错误信息拆出来catch (SQLException x) { int SQLCode x.getErrorCode(); // 数据库厂商错误码 String SQLState x.getSQLState(); // SQL 标准状态码 String message x.getMessage(); // 数据库返回的描述文本 String tip 未知错误; if (x instanceof SQLIntegrityConstraintViolationException) { tip 违反约束; } else if (x instanceof SQLDataException) { tip 类型不匹配或数字溢出; } else if (message.contains(token) || message.contains(符号)) { tip SQL 语句中遇到非法符号; } JOptionPane.showMessageDialog(null, tip \n message); }getErrorCode()是数据库自己的错误码getSQLState()是跨数据库的标准状态码。分类判断不能只依赖某一个字段因为同一种状态码可能对应不同场景报告里做的其实是组合判断——先看异常类型再看错误信息关键字最后弹窗给用户一个可读的中文提示。工程上如果你不想依赖异常子类型也可以维护一张错误码映射表把数据库错误码映射到违反约束/类型不匹配/溢出等类别这样换数据库时只需要改映射表。有一个值得养成的习惯在 catch 里把x.getStackTrace()的顶部几帧打出来往往能直接看出溢出发生在setInt还是executeUpdate。报错信息只说类型转换溢出但代码位置需要靠堆栈去定位这一步省不得。5. 避坑排查权限、命名、事务、别名冲突的五个高频坑5.1 权限不足db2admin 访问 EMPLOYEE 报无权限现象程序里查询 EMPLOYEE 表报 db2admin 没有访问权限。 原因EMPLOYEE 表的属主是 USR01db2admin 只管实例没有该表的访问权。实例管理账号和表属主是两回事这个区别新手很容易忽略。 解决用 USR01 登录数据库后执行授权查询只需要 SELECT做实验涉及更新时把 INSERT、UPDATE 一并授过去。排查顺序建议先确认当前用户和表属主再补授权不要直接给 db2admin 授 DBA 级别的全局权限权限面太大。-- 查看当前连接用户 select current user from sysibm.sysdummy1; -- 确认表属主 select tabschema, tabname from syscat.tables where tabname EMPLOYEE;5.2 未定义的名称TEMPL 被解析到了错误的模式现象执行程序时提示 TEMPL 是一个未定义的名称。 原因SQL 里只写了表名没有带模式前缀数据库按当前默认模式解析名字找不到对象。同一张表在不同模式名下可以分别存在不带前缀就依赖连接的默认模式。 解决把 SQL 改成USR01.TEMPL或者在连接 URL 里指定currentSchemaUSR01。从那以后我只要在实验或项目里写表名一律带全模式名宁肯多敲几个字符也不赌默认值。5.3 更新成功但数据库没变自动提交关闭却没提交现象executeUpdate返回值正常程序也没报错但重新查数据库发现数据没变。 原因setAutoCommit(false)之后没有显式commit()程序结束或连接关闭时未提交事务被回滚。 解决要么把setAutoCommit改成 true要么在成功路径上调用sample.commit()失败路径上调用sample.rollback()。验证方法前面提过同一连接内能查到改动、新连接查不到就是事务没提交的最明显信号。5.4 无法创建表同名别名占住了名字现象执行create table templ报错表建不起来。 原因之前为 employee 创建过名为 templ 的别名。别名和表在同一个命名空间里数据库不允许重名。 解决先删除别名再建表。排查时可以查系统目录确认名字是被哪种对象占用的避免误删。-- 查看 TEMPL 这个名字被什么类型的对象占用 select name, type from syscat.tables where name TEMPL; -- 如果确认为别名先删除 drop alias TEMPL;5.5 部分输入溢出相同 SQL 不同参数结果不同现象同一个更新 SQL输入 15 报溢出、16 更新 0 行、20 执行成功但数据没变。 原因这是三个独立问题叠在了一起。15 的工资乘完涨幅后超出列精度16 在表里根本没有对应记录20 是事务没提交。 解决先用describe table查列精度确认溢出原因再用查询语句确认输入值是否有匹配记录最后检查事务提交。这条坑提醒一个排查原则现象相似不等于原因相同一次只改一个变量逐个排除。6. 进阶验证技巧SQLWarning、任意行列修改与排查习惯6.1 SQLWarning 的读取链getWarnings 与 clearWarnings任务的最后一部分要求补全 labstaff1.java其中涉及 SQLWarning 的处理。SQLWarning 是 SQLException 的子类区别在于它只警告不阻断执行。它可能挂在 Connection、Statement、ResultSet 上读取方式是链式的Class.forName(COM.ibm.db2.jdbc.app.DB2Driver); // 以环境实际驱动类为准 Connection sample DriverManager.getConnection(JDBC:db2:sample, db2admin, db2admin); SQLWarning SQLWarn null; PreparedStatement stmt sample.prepareStatement( select id, name, salary from USR01.staff where Dept ?); stmt.setInt(1, mydeptno); ResultSet rs stmt.executeQuery(); if ((SQLWarn stmt.getWarnings()) ! null) { System.out.println(警告: SQLWarn.getMessage()); SQLWarn SQLWarn.getNextWarning(); // 链式往下读 } while (rs.next()) { System.out.println(rs.getInt(id) , rs.getString(name)); }getWarnings()返回第一条警告getNextWarning()逐条往下取。问题在于警告是粘在对象上的如果不及时读取后续操作可能覆盖前面的警告。读完一段就调用clearWarnings()清一下避免误判。这个技巧在排障时很管用很多数据看起来对但行为不对的玄学问题其实都是被吞掉的警告。6.2 结果集的任意行列修改可滚动游标与 updateRow另一个进阶要求是实现结果集中任意行、任意列的修改。JDBC 里对应的是可滚动、可更新的结果集Statement stmt sample.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs stmt.executeQuery(select id, name, salary from USR01.staff); rs.absolute(3); // 跳到第 3 行 rs.updateString(name, 更新后); // 修改当前行 name 列 rs.updateRow(); // 提交到数据库创建 Statement 时必须带TYPE_SCROLL_INSENSITIVE可滚动和CONCUR_UPDATABLE可更新两个参数缺一个都不支持按行修改。absolute(n)是绝对定位relative(n)是相对定位还有first()、last()。修改当前行用updateXxx系列方法但只改结果集不算数必须再调updateRow()才真正写入数据库。这套能力拿来做 GUI 数据编辑正合适表格选中的行号映射到absolute选中的列名映射到updateXxx的类型。从那以后我每次写完 JDBC 更新类代码都强制走一遍三连检查先确认表名带模式前缀再确认事务要么autoCommit要么显式commit最后用describe table查一下列的精度再决定参数范围。权限报错先查属主名字报错先查命名空间数据没变先查提交——这三板斧基本能覆盖大部分 JDBC 实验和真实开发里的翻车现场。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑