简介:面向高校数据库课程学习者与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 | 表、别名的归属模式,代码里必须写全 |
| 连接 URL | jdbc: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 方法翻译成数据库协议消息;第四层是数据库引擎,做权限校验、语法解析、约束检查和事务管理。
这个分层不是为了好看,而是为了定位错误。权限类报错发生在第四层,特征是报错信息里带对象名和账号名;对象名解析不到也发生在第四层,但报错里会出现一个「未定义的名称」,这时候要回头看代码里写的模式前缀对不对;驱动类找不到发生在第三层,报ClassNotFoundException;SQL 语法错误在第二层和第四层之间来回,SQLException里会带SQLState。写报告时把每条错误归到某一层,调试过程就不再是流水账。
提示:报告里的「调试过程」不要只贴截图,每条错误按「报错现象 → 归到哪一层 → 改了什么」三行写完,比十张截图更有说服力。
2.3 八个任务归并成四个模块
任务一到任务八如果按顺序一个个写,报告会变成八份互不相干的小结。我一般把它们归成四个模块,每个模块对应一组能力,报告章节也按模块切,读起来是一条线而不是八个点。
| 模块 | 覆盖任务 | 核心能力 | 交付物 |
|---|---|---|---|
| 连接与查询 | 任务 1、2、8 前半 | 驱动加载、连接建立、Statement、ResultSet 遍历、警告读取 | 查询类程序 + 结果截图 |
| 更新与事务 | 任务 3 | PreparedStatement、手动提交、受影响行数判断 | 更新类程序 + 溢出分析 |
| 交互与批量写入 | 任务 4、5、6、8 后半 | JOptionPane、单行/多行/子查询插入、空值显示、任意行列修改 | 带界面的增改查程序 |
| 异常与健壮性 | 任务 7 | SQLException 分类、对话框提示 | 四类错误的触发截图 |
归并之后有个好处:任务 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 的null,getInt返回 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的差别。第三处会问「可更新结果集的限制」,答聚合列和多表连接通常不可直接更新。这三个问题答顺了,基本能覆盖八成的追问。
还有一个容易被忽略的细节:报告里的截图要有上下文。只截一个成功的结果集,看不出你做了什么;截「输入值 + 执行结果 + 数据库里的实际数据」三段,才构成完整证据链。我吃过一次亏,交上去的截图里没有输入值,导师让我当场重跑,结果因为测试数据被改过,跑出来的数对不上,场面很难看。从那以后我每次做数据库类实验,都强制走一遍「操作前查一次、操作后查一次、把两次结果都留在报告里」的流程,哪怕多花十分钟。希望这套结构帮到你,照着填自己的任务和数据就行。
本文还有配套的精品资源,点击获取