JDBC连接DB2数据库实验:查询、事务、异常处理与验收避坑 简介面向高校数据库课程学习者与Java/JDBC初学者这份吉林大学《数据库应用程序开发》实验报告围绕DB2环境下的JDBC编程展开可用于课程实验对照、调试复盘与期末整理。包内为1个doc文档压缩包约1.76MB集中呈现完整实验记录与结果截图说明。已有903人学习内容覆盖连接SAMPLE数据库、创建Statement与PreparedStatement、执行查询和更新、遍历ResultSet、关闭自动提交及事务提交等关键环节也记录了权限授予、对象名错误、salary更新溢出、GUI输入输出、单行/多行/子查询插入、空值显示与约束异常处理等排错过程。适合需要按任务梳理JDBC操作脉络、理解DB2交互细节并积累实验报告写法的读者参考。1. 从一次权限报错说起这套 JDBC 实验方案要解决什么数据库应用程序开发这门课的实验报告难点从来不在 SQL 写不出来而在第一次把 Java 程序和 DB2 接上之后的那一串报错权限不足、对象名解析不到、改完数据没生效、数字太大直接溢出。这套方案围绕八个递进任务展开从补全一段 JDBC 查询代码到做出带 JOptionPane 交互的增改查界面再到把异常按约束违反、类型不匹配、溢出、非法符号四类分流提示。适合正在做同类课程设计或毕设数据访问层的人照着任务顺序写能交出一份有调试记录、有复现步骤、有截图位置的完整报告照着模块划分改能直接搬进自己的小系统。下面把环境、调用链、代码骨架、翻车点和验收口径一次讲清。2. 环境与总体设计从 Java 进程到 DB2 引擎的四层调用链实验报告写得散多半是因为一开始没有把「谁在调用谁」画清楚。八个任务看起来是八个独立小程序实际上共用同一条链路Java 代码调用 JDBC 接口驱动把调用翻译成数据库能懂的消息数据库引擎执行后再把结果集一层层回传。把这条链路先定下来后面每个任务就只是在这条链路上换一段 SQL、换一种结果处理方式报告的结构自然也就立住了。2.1 环境清单与连接参数约定动手前先把环境固定成一张表写进报告的第一节。参数一旦定下来就别中途改否则后面每个任务的截图都对不上答辩时被追问会很难解释。示例里的对象统一建在一个专用模式下下面一律写作LAB实际做的时候换成自己的模式名即可。项目取值说明数据库实例SAMPLE课程自带的示例库员工表 STAFF、EMPLOYEE 都在里面应用账号db2admin执行 Java 程序时使用的连接账号对象模式LAB表、别名的归属模式代码里必须写全连接 URLjdbc:db2:sample协议前缀大小写不敏感惯例写小写驱动类本地COM.ibm.db2.jdbc.app.DB2Driver需要本机装有数据库客户端驱动类纯网络com.ibm.db2.jcc.DB2Driver只带 jar 包也能连换机器更方便交互方式命令行 JOptionPane后半段任务全部转到对话框驱动 jar 必须进 CLASSPATH这一点在报告里要写清楚因为「驱动加载失败」和「连接失败」的报错长得很像后面排查章节会专门说。账号密码直接写在代码里只适合实验环境真要交付的方案通常抽到配置文件这是课程作业和工程习惯之间需要主动说明的一处差异。2.2 四层调用链每一层出错长什么样从上往下拆第一层是业务代码负责拼 SQL、拿结果、弹对话框第二层是 JDBC 接口Connection、Statement、PreparedStatement、ResultSet这些对象都在这一层第三层是驱动把 Java 方法翻译成数据库协议消息第四层是数据库引擎做权限校验、语法解析、约束检查和事务管理。这个分层不是为了好看而是为了定位错误。权限类报错发生在第四层特征是报错信息里带对象名和账号名对象名解析不到也发生在第四层但报错里会出现一个「未定义的名称」这时候要回头看代码里写的模式前缀对不对驱动类找不到发生在第三层报ClassNotFoundExceptionSQL 语法错误在第二层和第四层之间来回SQLException里会带SQLState。写报告时把每条错误归到某一层调试过程就不再是流水账。提示报告里的「调试过程」不要只贴截图每条错误按「报错现象 → 归到哪一层 → 改了什么」三行写完比十张截图更有说服力。2.3 八个任务归并成四个模块任务一到任务八如果按顺序一个个写报告会变成八份互不相干的小结。我一般把它们归成四个模块每个模块对应一组能力报告章节也按模块切读起来是一条线而不是八个点。模块覆盖任务核心能力交付物连接与查询任务 1、2、8 前半驱动加载、连接建立、Statement、ResultSet 遍历、警告读取查询类程序 结果截图更新与事务任务 3PreparedStatement、手动提交、受影响行数判断更新类程序 溢出分析交互与批量写入任务 4、5、6、8 后半JOptionPane、单行/多行/子查询插入、空值显示、任意行列修改带界面的增改查程序异常与健壮性任务 7SQLException 分类、对话框提示四类错误的触发截图归并之后有个好处任务 6 的「空值显示」其实横跨查询和界面两块放在交互模块里讲最顺任务 8 拆成两半前半的 SQLWarning 归到查询模块后半的可滚动结果集归到交互模块逻辑上就不会打架。报告的目录结构直接照这张表展开导师一眼能看出你做过整体设计而不是被任务牵着走。3. 查询链实现连接、结果集与空值显示的完整写法查询是所有后续任务的地基任务 1 和任务 2 表面上只是补几行代码实际上把 JDBC 查询的固定套路走了一遍加载驱动、建连接、建语句对象、执行、遍历结果集、关闭。这套骨架后面会被反复复用写扎实一次更新和界面部分就只是换对象。3.1 三段式骨架与最容易写错的两个参数先看授权部分。任务 1 的第一个报错是应用账号没有 EMPLOYEE 表的访问权限这类问题要在数据库侧解决不是在 Java 里加重试。-- 以对象属主身份登录把表的访问权限授予应用账号 CONNECT TO SAMPLE USER LAB; GRANT SELECT, INSERT, UPDATE, DELETE ON LAB.STAFF TO USER db2admin; GRANT SELECT ON LAB.EMPLOYEE TO USER db2admin;授权语句里权限列表要按实际需要给查询任务只给 SELECT 就够后面更新任务再补 UPDATE。给全权限虽然省事但报告里体现不出你对最小权限的理解。第二个报错是「未定义的名称」代码里写了TEMPL而库里实际是LAB.TEMPL原因是当前连接用户的默认模式不是你建表时用的模式解决办法是代码里一律写全模式名.表名不要依赖默认解析。// 1. 加载驱动类名写错会抛 ClassNotFoundException与连接失败要区分开 Class.forName(COM.ibm.db2.jdbc.app.DB2Driver); // 2. 建立连接三个参数分别是 URL、账号、口令 Connection sample DriverManager.getConnection( jdbc:db2:sample, db2admin, db2admin); // 3. 建语句对象并执行查询表名带模式前缀 Statement stmt sample.createStatement(); ResultSet rs stmt.executeQuery( select ID, NAME, JOB, SALARY from LAB.STAFF where DEPT 10); // 4. 遍历结果集next() 返回 false 表示没有下一行 while (rs.next()) { System.out.println(rs.getInt(ID) \t rs.getString(NAME)); }这段代码有两个参数值得在报告里单独说明。getConnection的第一个参数是 URL不是驱动类名两者写混是最常见的低级错误executeQuery只用于返回结果集的 SELECT写 UPDATE 会直接抛异常更新要用executeUpdate。另外连接、语句、结果集三个对象原则上要按相反顺序关闭实验代码里图省事可以不写 finally但报告里要说明生产写法应该是 try-with-resources这是加分项。3.2 结果集遍历与「空」值统一显示任务 6 要求把结果集中所有为空的列显示成「空」字这件事看着小做错了很扎眼。JDBC 里取到 NULL 有两种表现getString返回 Java 的nullgetInt返回 0 并且wasNull()为 true。如果只判null数值列的空值会被显示成 0看起来像真实数据。// 统一取值方法先取值再用 wasNull 判断数据库里是不是 NULL private static String show(ResultSet rs, String col) throws SQLException { String v rs.getString(col); // 数值列也能用 getString 取 if (v null || rs.wasNull()) { // 双保险避免 0 和 NULL 混淆 return 空; } return v.trim(); }把取值收敛到一个方法里界面上每个单元格都调它空值处理就不会漏。这里用getString统一取值是有意为之显示层本来就要字符串数值转换放到需要计算的地方再做可以减少类型转换异常。报告里可以补一句如果这一列后面要参与计算就得用对应的getInt、getBigDecimal并配合wasNull()不能偷懒。3.3 SQLWarning 的链式读取任务 8 里有一段容易被忽略的代码读取SQLWarning。它和异常不是一回事警告不会中断执行但会挂在语句对象上比如数据被截断、某个条件没生效。写法是拿到第一个警告后顺着nextWarning()往下走形成一条链。// 执行完查询后检查警告链注意 getWarnings 取的是最近一次操作的警告 SQLWarning w stmt.getWarnings(); while (w ! null) { System.out.println(警告: w.getErrorCode() / w.getMessage()); w w.nextWarning(); // 沿链往下直到没有下一个 }这段代码在报告里的价值不在于跑出结果而在于说明你区分了「错误」和「警告」两种反馈。实际调试时它常常是空的属于玄学——没警告不代表没问题有警告也不代表失败。我一般会在报告里明确写一句警告链为空是正常情况触发条件通常是插入超长字符串被截断想复现可以故意往短字段里塞长数据。4. 更新与事务PreparedStatement、提交开关与界面化改造任务 3 是整个实验里信息量最大的一段。它同时踩了三个坑数字放大之后溢出、按部门更新影响行数为 0、更新执行成功但数据库没变。这三个现象背后是三种完全不同的原因能拆清楚报告的技术含量就上来了。4.1 手动事务的骨架与 commit 的配对先看骨架。关闭自动提交之后所有修改都挂在事务里必须显式提交才落库这是一把双刃剑给了你回滚的后悔药也埋下了「改成功但没生效」的伏笔。Connection sample DriverManager.getConnection( jdbc:db2:sample, db2admin, db2admin); sample.setAutoCommit(false); // 关闭自动提交进入手动事务 String sql update LAB.STAFF set SALARY SALARY * ? where DEPT ?; PreparedStatement pstmt sample.prepareStatement(sql); pstmt.setBigDecimal(1, factor); // 放大系数用 BigDecimal 避免精度丢失 pstmt.setInt(2, mydeptno); // 部门号对应 SQL 里第一个占位符之后的位置 try { int updateCount pstmt.executeUpdate(); if (updateCount 0) { sample.rollback(); // 一行都没改到回滚并提示 } else { sample.commit(); // 有实际影响才提交 } } catch (SQLException x) { sample.rollback(); // 任何异常都先回滚再交给上层提示 throw x; }参数说明要写进报告setBigDecimal的第一个参数是占位符序号从 1 开始不是 0放大系数用BigDecimal而不是double是因为金额类字段用浮点会出现精度误差。executeUpdate的返回值是受影响行数这个数字非常关键——它为 0 说明 WHERE 条件没匹配到任何行不是程序出错而是数据里根本没有这个部门。4.2 溢出与「没生效」的两次归因任务 3 里最值得写进报告的是那组对比实验把放大系数改成 2输入 15 和 20 都溢出改成 1.01两个都不溢出。这一步证明了溢出不是随机现象而是放大后的数值超出了 SALARY 字段的表示范围。归因方法很朴素——控制变量一次只改一个因子然后观察结果是否稳定复现。第二个现象更有意思输入 16 时显示修改了 0 行输入 20 时执行成功但数据库没变化。前者去数据库里直接查一下where DEPT 16发现确实没有记录问题出在测试数据选得不对不是代码错。后者查数据库发现值可以改说明数据库侧没问题问题在 Java 侧——setAutoCommit(false)之后没有调commit()连接关闭时事务被回滚了。这两条要分开写因为一个是数据问题一个是事务问题混在一起就成了玄学。注意手动事务模式下程序正常结束但没 commit数据和没执行一样。报告里要把「执行成功」和「已提交」当成两个概念分别验证。4.3 三种插入入口单行、多行与子查询任务 5 要求界面上实现三种插入方式。它们的差别不在界面而在 SQL 的组织方式单行插入是一条带占位符的 INSERT多行插入是循环执行同一条 PreparedStatement 或者用批量提交子查询插入则是把 SELECT 的结果直接灌进目标表。-- 子查询插入源表和目标表列顺序必须一一对应 INSERT INTO LAB.TEMPL (ID, NAME, SALARY) SELECT ID, NAME, SALARY FROM LAB.STAFF WHERE DEPT 20;子查询插入这条最容易翻车的地方是列数不匹配和类型不匹配报错信息往往只说「不符合」不告诉你是哪一列。解决办法是把 SELECT 的列显式写出来不用select *顺序和目标列对齐。多行插入在界面上通常是一个多行文本框按行拆分后循环addBatch()、最后executeBatch()比逐条执行快但要注意批量执行时任何一条失败整批的行为取决于驱动和事务设置报告里说明这一点会显得你考虑过边界。界面上还遇到过一次建表失败想创建LAB.TEMPL却报已存在原因是之前给 EMPLOYEE 建过一个叫 TEMPL 的别名名字被占用了。删掉别名再建表就好。这类「名字冲突」在数据库里很常见表、视图、别名共享同一个命名空间报告里写清楚这个原因比只写「删掉就好了」专业得多。4.4 任意行任意列的定位与更新任务 8 的后半段要求能修改结果集中任意行、任意列。用普通结果集做不到要建可滚动、可更新的结果集。// 两个常量分别指定可前后滚动、可用来更新底层表 Statement stmt sample.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rs stmt.executeQuery(select ID, NAME, SALARY from LAB.STAFF); rs.absolute(rowIndex); // 直接跳到第 rowIndex 行从 1 开始 rs.updateBigDecimal(SALARY, newVal); // 只改这一行的这一列 rs.updateRow(); // 写回数据库缺这一步等于没改三个常量要讲清TYPE_SCROLL_INSENSITIVE表示可以随意前后移动但不能感知别人对数据的修改CONCUR_UPDATABLE表示结果集可以回写absolute的行号从 1 开始。updateRow()是必须的只调updateXxx只是改了结果集里的副本。另外可更新结果集对 SQL 有要求多表连接、聚合列通常不能直接更新报告里点一句可以避免验收时被问住。4.5 四类异常的识别与对话框提示任务 7 要求把操纵类错误用对话框提示并且要覆盖约束违反、类型不匹配、溢出、非法符号四种。与其在界面上到处try-catch不如写一个统一的转换方法按SQLState分类给不同文案。SQLState 前缀典型含义提示文案方向23完整性约束违反主键重复、非空、外键提示哪类约束不满足建议检查输入22003 / 22001数值超范围、字符串超长提示数值过大或内容过长给出字段范围42804 / 22018类型不匹配、转换失败提示该列需要什么类型42601语句语法错误常见于非法符号提示 SQL 拼装有问题属于程序缺陷catch (SQLException x) { String state x.getSQLState() null ? : x.getSQLState(); String msg; if (state.startsWith(23)) { msg 数据违反约束请检查主键是否重复或必填项是否为空; } else if (state.startsWith(22003)) { msg 数值超出字段范围请调小后重试; } else if (state.startsWith(42804) || state.startsWith(22018)) { msg 数据类型不匹配请检查输入格式; } else { msg 数据库返回错误 x.getErrorCode() x.getMessage(); } JOptionPane.showMessageDialog(null, msg, 操作失败, JOptionPane.ERROR_MESSAGE); }分类的意义在于约束违反是用户输入问题提示要可操作语法错误是程序缺陷不该让用户去改输入。报告里把这条边界写出来说明你区分了「可恢复错误」和「程序 bug」。四类错误都要留下触发截图截图前把输入值记下来写进报告否则过两天自己都复现不了。5. 避坑与排查八个任务里最容易翻车的五个点实验报告的技术深度很大一部分体现在「踩过的坑」写得多具体。下面五条是这套任务里复现率最高的每条都按现象、原因、解决写可以直接对着改。5.1 应用账号没有表权限现象是程序一连上就报权限不足报错信息里带账号名和表名换成管理员账号跑就正常。原因是建表和授权用的不是同一个账号对象属主是LAB程序却用db2admin连。解决方式是在数据库侧执行 GRANT把需要的权限逐条授给应用账号而不是图省事给 DBA 权限。报告里把授权语句和授权前后的报错截图放一起因果关系最清楚。5.2 表名不带模式导致「未定义的名称」现象是代码里写TEMPL能编译通过运行时报未定义的名称去数据库里查表确实存在。原因是当前连接的默认模式与建表模式不一致解析器按默认模式去找自然找不到。解决办法是所有 SQL 里的表名一律写全模式名.表名别名和视图同理。这条坑在换机器、换账号时特别容易复现报告里值得单独留一段。5.3 名字冲突别名占了表的位置现象是建表时报对象已存在但用列表命令查不到同名表。原因是之前为别的表建过同名别名表、视图、别名在同一个命名空间里抢名字。解决是先删别名再建表或者直接换个名字。这类问题在报告里体现的是你对数据库对象模型的理解比单纯贴一句报错有价值。5.4 更新成功但数据没变现象是executeUpdate返回了正数程序也没抛异常去数据库一查值还是老的。原因是连接关闭了自动提交代码里从头到尾没有commit()连接关闭时事务回滚。解决办法是判断受影响行数后显式提交异常分支显式回滚。排查手法很直接在提交前后各查一次数据如果提交前能查到新值、提交后查不到问题一定在提交环节。5.5 数值溢出与「零行受影响」被当成同一个错现象是输入某些部门号时报溢出输入另一些时提示改了 0 行两个现象混在一起很难判断。原因是前者是数值放大后超出字段范围后者是 WHERE 条件压根没匹配到记录。解决办法是先用 SQL 在数据库里确认该条件有没有数据再单独验证放大系数一次只改一个变量。这条经验很血泪把两个独立问题当成一个 bug 查能耗掉一整晚。6. 验收与答辩怎么证明这套程序真的跑通了验收时最怕的不是功能少而是说不清哪个功能是自己做的、边界在哪。我一般按「能复现、能解释、能兜底」三层准备每一层都对应具体的检查动作答辩前逐条走一遍基本不会被问住。检查项操作方法通过标准连接可复现换一台机器只带 jar 和源码运行不依赖本机残留配置也能连上查询正确性用一个有数据的部门号和一个空部门号各跑一次前者出行后者出 0 行且不报错空值显示造一条含 NULL 的记录再查界面显示「空」不显示 0 或 null事务完整性更新后立刻另开一个连接查数据已变更说明提交生效异常分流分别触发约束、类型、溢出、语法四类错误四类提示文案不同且可操作三种插入单行、多行、子查询各执行一次目标表记录数与预期一致答辩提问高频集中在三处。第一处会问「为什么关自动提交」答法是把提交和回滚的控制权拿回来并举例说明溢出时回滚避免了脏数据。第二处会问「空值和 0 怎么区分」答wasNull()顺带说清getString和getInt的差别。第三处会问「可更新结果集的限制」答聚合列和多表连接通常不可直接更新。这三个问题答顺了基本能覆盖八成的追问。还有一个容易被忽略的细节报告里的截图要有上下文。只截一个成功的结果集看不出你做了什么截「输入值 执行结果 数据库里的实际数据」三段才构成完整证据链。我吃过一次亏交上去的截图里没有输入值导师让我当场重跑结果因为测试数据被改过跑出来的数对不上场面很难看。从那以后我每次做数据库类实验都强制走一遍「操作前查一次、操作后查一次、把两次结果都留在报告里」的流程哪怕多花十分钟。希望这套结构帮到你照着填自己的任务和数据就行。本文还有配套的精品资源点击获取