简介这份实验报告面向高校数据库课程学习者与Java JDBC初学者聚焦吉林大学《数据库应用程序开发》课程的完整实验过程帮助读者理解数据库与Java程序交互的核心方法。报告围绕JDBC连接、Statement与PreparedStatement使用、结果集遍历、事务提交控制及异常处理展开并记录了权限授予、对象名称不匹配、数值溢出与事务未提交等典型调试过程配有运行结果截图可作为实验复盘与排错参考。资源包共1个doc文档约1.76MB内容按任务分节组织便于对照查阅。目前已有902人学习下载适合需要完成同类实验或巩固JDBC编程基础的读者参考借鉴。1. 一份把 JDBC 从“能跑”逼到“敢改”的实验报告数据库这门课很多人卡在“SQL 写得挺顺一接 Java 就懵”。这份《数据库应用程序开发》实验报告恰好把这条断点补上了它围绕 DB2 的 SAMPLE 库和 JLU 模式用八个任务把 JDBC 的连接、查询、更新、事务、GUI、异常处理串成一条线。适合两类人——正在做课程设计、需要一份能照着复现的 JDBC 全流程参考的在校生以及工作里要维护老系统、手头还是 DB2 JDBC 这套组合的从业者。它不是 API 手册而是一份带调试记录的“翻车现场”权限、别名、事务提交、数值溢出这些坑都留了痕迹照着走能少走不少弯路。2. JDBC 连接与查询从权限报错到 ResultSet 遍历2.1 先解决“连得上”驱动、URL 与权限三件事JDBC 程序跑不起来九成问题不在 Java 代码而在“连不上”或“连上了没权限”。这份报告里第一个典型错误就是db2admin没有访问EMPLOYEE表的权限解决办法是登录JLU用户把访问权限授予db2admin。这背后是 DB2 的模式schema隔离机制表建在JLU模式下db2admin默认只能看到自己模式下的对象跨模式访问必须显式授权。常见做法是先确认三件事驱动类名、JDBC URL、账号权限。DB2 的 JDBC URL 形如jdbc:db2:sample其中sample是数据库名驱动类在老版本里是COM.ibm.db2.jdbc.app.DB2Driver新版本一般用com.ibm.db2.jdbc.app.DB2Driver或 Type 4 的com.ibm.db2.jdbc.net.DB2Driver。报告里用的是前者属于 Type 2 驱动依赖本地 DB2 客户端这也是为什么它能在实验环境里直接跑通。授权这一步SQL 层面就是一条 GRANT-- 以 JLU 用户登录后执行把 EMPLOYEE 表的查询权限授予 db2admin GRANT SELECT ON JLU.EMPLOYEE TO USER db2admin; -- 如果后续任务要改数据还需要 UPDATE 权限 GRANT UPDATE ON JLU.STAFF TO USER db2admin;这里的关键参数是JLU.EMPLOYEE这种“模式名.表名”的完整限定写法。很多新手只写EMPLOYEE在当前用户模式下找不到就报“未定义名称”报告里第二个错误正是这个命令行执行时提示DB2ADMIN.TEMPL未定义把代码里的TEMPL改成JLU.TEMPL就好了。这不是玄学是 DB2 的默认模式解析规则——不加限定就按当前连接用户的模式去找。2.2 查询四步走Connection、Statement、ResultSet、next任务 2 的labstaff.java把 JDBC 查询的标准骨架完整走了一遍四步缺一不可。第一步建连接第二步建 Statement第三步执行查询拿 ResultSet第四步用next()逐行遍历。报告里给出的代码片段很朴素但每一步都有讲究import java.sql.*; public class LabStaff { public static void main(String[] args) { try { // 1. 加载 DB2 驱动Type 2 驱动依赖本地客户端 Class.forName(COM.ibm.db2.jdbc.app.DB2Driver); // 2. 建立连接URL、用户名、密码 Connection sample DriverManager.getConnection( jdbc:db2:sample, db2admin, db2admin); // 3. 创建 Statement 对象 Statement stmt sample.createStatement(); // 4. 执行查询注意表名带模式限定 JLU. ResultSet rs stmt.executeQuery( select NAME, JOB, SALARY from JLU.STAFF where ID 10); // 5. 移动到第一行next() 返回 false 表示没有数据 boolean more rs.next(); while (more) { System.out.println(rs.getString(NAME) rs.getString(JOB) rs.getDouble(SALARY)); more rs.next(); } // 6. 按打开逆序关闭资源 rs.close(); stmt.close(); sample.close(); } catch (Exception e) { e.printStackTrace(); } } }逻辑说明Class.forName触发驱动注册DriverManager.getConnection返回连接对象createStatement创建执行器executeQuery只用于 SELECT 并返回 ResultSet。参数上getString按列名取值比按索引更可读但列名大小写在 DB2 里默认不敏感写成name或NAME都行getDouble对应SALARY的数值类型如果列是DECIMAL用getBigDecimal更稳。一个容易被忽略的点是next()的语义结果集初始游标在第一行之前第一次next()才移到第一行返回true表示有数据。报告里先boolean more rs.next()再进循环这个写法是对的但很多人会写成while(rs.next())直接遍历两种都行前者适合需要先判断“有没有结果”再决定后续逻辑的场景。提示Type 2 驱动要求运行机器上装了 DB2 客户端并配置了实例纯远程连接建议换 Type 4 驱动URL 改成jdbc:db2://host:port/sample类名换成com.ibm.db2.jdbc.net.DB2Driver。3. 更新与事务PreparedStatement、溢出与“改了没生效”3.1 手动事务setAutoCommit(false) 是双刃剑任务 3 的labupdate.java引入了两个新东西PreparedStatement 和手动事务。报告里明确写了sample.setAutoCommit(false)然后创建 PreparedStatement、设参数、执行executeUpdate()。这一步的意图是让多条更新要么全成、要么全回滚但报告里紧接着就踩了一个大坑输入 20 或 30 时程序显示执行成功数据库却没变化。原因很直接——关了自动提交却没调commit()。JDBC 默认autoCommittrue每条语句执行完立即提交一旦设成false就必须显式commit()或rollback()否则连接关闭时未提交的改动会被丢弃。报告里的解决办法是把false改回true这能跑通但不是事务的正确用法。更稳妥的写法是保留手动事务在成功路径上提交、异常路径上回滚Connection sample DriverManager.getConnection( jdbc:db2:sample, db2admin, db2admin); sample.setAutoCommit(false); // 关闭自动提交开启事务 try { String sql update JLU.STAFF set SALARY SALARY * ? where DEPT ?; PreparedStatement pstmt sample.prepareStatement(sql); pstmt.setDouble(1, 1.01); // 参数 1涨幅系数 pstmt.setInt(2, mydeptno); // 参数 2部门号 int updateCount pstmt.executeUpdate(); System.out.println(affected rows: updateCount); sample.commit(); // 成功才提交 } catch (SQLException x) { sample.rollback(); // 失败回滚避免半截数据 System.out.println(SQLCode x.getErrorCode() SQLState x.getSQLState() Message x.getMessage()); } finally { sample.setAutoCommit(true); sample.close(); }参数说明setDouble(1, 1.01)的索引从 1 开始对应 SQL 里第一个?executeUpdate()返回受影响行数返回 0 不代表出错只代表没有匹配记录。异常处理里getErrorCode()拿 DB2 的 SQLCodegetSQLState()拿五字符状态码getMessage()拿可读信息这三个是排查数据库错误的标准三件套。3.2 溢出与“0 行”的两种真相报告里最有价值的一段调试是溢出分析。输入 15 和 20 时用salary*2会溢出换成salary*1.01就不溢出由此证明是修改后的数值超出了SALARY列的类型范围。DB2 的DECIMAL或SMALLINT列有精度上限乘法放大后超限就抛类型转换溢出。这个排查思路值得学用不同放大系数做对照实验把“是不是数值太大”这个假设验证掉。另一个真相是“输入 16 反馈修改了 0 行”。报告里查了数据库发现确实没有对应记录所以 0 行是正常结果不是 bug。这两件事经常被混为一谈——溢出是异常0 行是空匹配前者要 catch后者要在业务逻辑里判断updateCount 0并给出提示。注意executeUpdate返回 0 时不要默认当成功先确认 WHERE 条件是否真的能匹配到行涉及金额、薪资的乘法更新先查列定义精度再决定系数。4. GUI 与批量插入JOptionPane 和子查询插入怎么落地4.1 用 JOptionPane 把控制台程序改成对话框任务 4 要求把labupdate.java改成 GUI 形式输入和输出都走JOptionPane。这不是让你学 Swing 全套而是用最轻的方式把“命令行交互”换成“弹窗交互”。核心就三个方法showInputDialog收输入、showMessageDialog报结果、showConfirmDialog做确认。改造时把原来Scanner或args的取值换成弹窗返回值注意输入是字符串要自己Integer.parseInt并处理NumberFormatException。import javax.swing.JOptionPane; String deptStr JOptionPane.showInputDialog(请输入部门号); if (deptStr null) return; // 用户点了取消 int mydeptno; try { mydeptno Integer.parseInt(deptStr.trim()); } catch (NumberFormatException e) { JOptionPane.showMessageDialog(null, 部门号必须是整数); return; } // ... 执行更新后 JOptionPane.showMessageDialog(null, 影响行数 updateCount);逻辑说明showInputDialog返回null表示用户取消必须判空否则parseInt(null)直接 NPE。trim()去掉首尾空格避免用户手滑输入 15。输出统一走showMessageDialog第一个参数传null表示居中显示。这套改法不追求界面美观追求的是“输入输出不依赖控制台”在实验和内部工具里够用。4.2 单行、多行、子查询插入的三种写法任务 5 要求向TEMPL表插入数据且要支持单行插入、多行插入、通过子查询插入。这里第一个坑是建表失败报告里说之前给EMPLOYEE建过别名TEMPL导致TEMPL表建不出来删掉别名才行。DB2 里别名和表共享命名空间同名就冲突这是血泪经验。三种插入对应三种 SQL 形态。单行插入用普通 INSERT多行插入可以循环执行 PreparedStatement 的addBatch()executeBatch()子查询插入用INSERT INTO ... SELECT ...数据来源是另一张表的查询结果。// 单行插入 String sql1 insert into JLU.TEMPL (ID, NAME, SALARY) values (?, ?, ?); PreparedStatement p1 sample.prepareStatement(sql1); p1.setInt(1, 101); p1.setString(2, 张三); p1.setDouble(3, 5000.0); p1.executeUpdate(); // 多行插入批量提交减少往返 String sql2 insert into JLU.TEMPL (ID, NAME, SALARY) values (?, ?, ?); PreparedStatement p2 sample.prepareStatement(sql2); for (int i 0; i rows.length; i) { p2.setInt(1, rows[i].id); p2.setString(2, rows[i].name); p2.setDouble(3, rows[i].salary); p2.addBatch(); // 加入批次 } int[] counts p2.executeBatch(); // 一次提交返回每行影响数 // 子查询插入从 STAFF 里筛数据灌进 TEMPL String sql3 insert into JLU.TEMPL (ID, NAME, SALARY) select ID, NAME, SALARY from JLU.STAFF where DEPT ?; PreparedStatement p3 sample.prepareStatement(sql3); p3.setInt(1, 20); p3.executeUpdate();参数说明addBatch()只是把当前参数集缓存executeBatch()才真正发送返回的int[]每个元素对应一条语句的影响行数出现EXECUTE_FAILED说明该条失败。子查询插入的列顺序必须和 SELECT 的列顺序一一对应类型也要兼容否则报类型不匹配。5. 避坑与排查五条从报错里抠出来的经验5.1 现象提示“未定义名称”代码里表名明明没错原因DB2 默认按当前连接用户的模式解析未限定表名表建在JLU模式下db2admin连接时找不到。解决SQL 里统一写JLU.表名或在连接后执行SET SCHEMA JLU切换默认模式。这条在报告里出现了两次属于高频坑。5.2 现象更新显示成功查库却没变化原因setAutoCommit(false)后没有commit()改动停留在事务里连接关闭即丢弃。解决成功路径显式commit()异常路径rollback()不要图省事把自动提交改回true掩盖问题。5.3 现象建表报“对象已存在”但表列表里没有原因同名的别名alias占用了命名空间DB2 里别名和表不能同名。解决先DROP ALIAS JLU.TEMPL再建表或者换个表名。排查时查SYSCAT.TABLES和SYSCAT.ALIASES两张系统表就能看清。5.4 现象数值更新抛类型转换溢出原因乘法放大后超出列精度比如SMALLINT上限 32767salary*2直接越界。解决先查列定义SYSCAT.COLUMNS里的TYPENAME、LENGTH再选放大系数业务上该用DECIMAL(10,2)就别用整型。5.5 现象结果集里空列显示null界面难看原因getString对 NULL 列返回 Java 的null直接拼接就打印“null”。解决用rs.wasNull()判断或封装一个取值方法NULL 统一返回“空”。任务 6 要求的就是这个改法是在取值后加一层判空别在 SQL 里用COALESCE硬凑展示层的事展示层解决。6. 异常分类与任意行修改把错误提示做成对话框任务 7 要求把违反约束、类型不匹配、溢出、非法符号这几类错误都用对话框提示。做法是在catch (SQLException x)里按SQLState或SQLCode分支再用JOptionPane.showMessageDialog弹出来。DB2 的约束违反通常是SQLState以23开头类型不匹配和溢出看SQLCode的具体值非法符号多半是 SQL 拼写问题。与其背错误码不如把getMessage()原样带上再补一句人话解释。catch (SQLException x) { String state x.getSQLState(); String tip; if (state ! null state.startsWith(23)) { tip 违反约束检查主键、外键或非空限制; } else if (state ! null state.startsWith(22)) { tip 数据类型不匹配或数值溢出; } else { tip SQL 执行失败请检查语句; } JOptionPane.showMessageDialog(null, tip \nSQLState state \n x.getMessage()); }任务 8 的“任意行、任意列修改”是这套实验的收尾难点。思路是用可滚动的 ResultSetcreateStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)然后rs.absolute(row)定位到指定行rs.updateString(NAME, newVal)改列最后rs.updateRow()落库。这里有个边界可滚动结果集对游标类型和驱动有要求Type 2 驱动支持得比较完整换驱动要先验证。另一个边界是updateRow()必须在结果集仍打开、且当前行有效时调用定位越界会抛异常。我自己的习惯是凡是涉及setAutoCommit(false)的代码写完先跑一遍“故意抛异常”的用例确认rollback()真的生效再跑正常路径。这份报告里“改了没生效”的坑本质就是没验证回滚和提交两条路径。从那以后我每次碰手动事务都强制走一遍提交和回滚的对照测试省得事后查黑匣子。希望帮到你。本文还有配套的精品资源点击获取