DB2
软件学院最具历史性的课程,可以与考古学院联动的那种。
包含两门课,其一为《企业级数据库性能调优》,其二为《数据库应用程序开发》。
据说这两门课之前是与IBM合作的产物,过了可以拿到一个证书,但是后来就没有证书了,不过课程还是保留下来了。
该课程需要使用Windows XP虚拟机。 为方便各位同学,猫娘整理了一些XP时代能使用的软件,以简化各项操作。详见后文。
相关资源
课程讲义、文档类
- 两门课程的PPT、相关课程资料等【其实直接在学习通下载更方便】百度网盘 蓝奏云【密码:2295】
- 课程要求编辑的单个文件代码,可能需要您在压缩包
cg113labfiles.zip中的CG113目录下自行寻找。受限于8.3文件名格式(详见文末附录),部分代码的文件名变为类似于LABUPD~1.JAV的形式,需要您自行修改文件名。 - 如需配置DB2服务器,使用现代Java连接,可参考
db2连接指南.pdf
旧版软件类
- XP SP3系统镜像【因蓝奏云100MB限制,只有百度网盘链接】【仅当虚拟机系统无法正常使用时才需要重新安装】百度网盘链接
- 一张当年番茄花园的系统盘,但是里面的XP是SP2版本,只使用里面附带的常用软件就好了 番茄花园下载链接
- 知乎专栏:WinXP开发宝典
- 以上软件资源,建议安装一个Notepad++和一个PDF阅读器即可。
代码
- 详见【代码】目录
- 管理系统所需依赖库在网盘的
数据库应用程序开发/lib.zip,解压至【员工管理系统】即可 - 需要您自行修改
ManagerMain.java中的数据库配置
企业级数据库性能调优
选修课-任选课,1.5学分(含1.5实践学分),推荐指数3.5颗星。
- 该课程没有任何技术难度,所有操作步骤均在PPT或者指导书上有提示。
- 甚至不需要你自己写代码,复制粘贴即可。但是可能由于这门课的东西太老,AI似乎并不能提供有效帮助。
- 每次课都有位置签到。
- 给分很高,而且算实践学分,但鉴于实验步骤比较繁琐,过于考古,且每次课都有签到,综合给到3.5分。
考核
- 平时成绩50分。实验报告1-5占40分,实验报告6占5分,国产数据库小论文占5分。
- 期末成绩50分。最后一次课上需要完成一个大作业(实际上就是5个简答题),PPT上都有,可以直接抄。如果没时间去还可以补考。
- 期末大作业限时90分钟。
真题1:2024.10.22
- 实验中创建artists表的时候,为什么把常规数据指定存储在DMS01表空间,索引指定存储在DMS02表空间,大对象则只能存储在DMS03表空间?这样做的好处是什么?
- 为表空间A在物理磁盘C上映射了5个containers,表空间B在物理磁盘D、E、F上分别映射了1个container,请问哪个表空间读写性能更好,为什么?
- 在import实验过程中,向artists表中导入数据时,应该成功导入79行,但有些同学只导入了36行,请分析有可能存在的问题。
- 日志有几种工作方式?你在进行数据库备份和恢复的实验时,分别对日志进行了怎样的设置?复原和前滚分别对应日志的哪种工作方式?
- 在实验中曾经编写下列语句:sql请解释该语句所实现的具体功能。分析该触发器能否阻止同一件商品背重复插入reorder表中?如果不能,请给出修改方案。
create trigger recorder after update of qty on stock referencing new as n for each row mode db2sql when (n.qty <= 5) insert into recorder values (n.itemno, current timestamp)
真题2:2024.10.24
- 实验中创建musicdb数据库时,指定的Page大小为4K,但在创建DMS01表空间时,指定extent size的大小为4Page,请问DMS01表空间分配存储空间的单位是多少K?不同表空间的extent size的大小可以不同吗?
- 为表空间A在物理磁盘C上映射了5个container,表空间B在物理磁盘D、E、F、G、H上分别映射了1个container,请问哪个表空间的读写性能更好?为什么?
- 在load实验过程中,为什么以repalce方式向artists表装载了78行之后,使得albums表和stock表不可用?如何解决这种Pending问题?
- 日志有几种工作方式?你在进行数据库备份和恢复的实验时,分别对日志进行了怎样的设置?复原和前滚分别对应日志的哪种工作方式?
- 请创建一个reorder触发器,该触发器在stock表的qty列上有修改,满足
qty<=5时触发,该触发器为后触发器,出发点动作是insert into reorder values(n.itemno, current timestamp),要求当多次满足条件时,只触发一次。
数据库系统应用程序开发
必修课,1.5学分,计入保研绩点。
依旧联动考古学院。
- 你需要使用Java5
- 虚拟机同上门课程,如果你选了上面那门课,可以直接沿用。
- 为什么不能用新版本的Java,首先是因为这玩意需要用XP虚拟机,要在那个虚拟机上面跑你的Java代码,里面只有Java5。你可能会想在自己物理机上面把虚拟机当数据库服务器连接,但是很遗憾,连接这个数据库会遇到各种问题,猫娘甚至专门在学习通讨论区发了个帖子描述了解决办法。
- 如果您确定要使用现代Java(猫娘也是这样做的),可参考猫娘给出的文档。
- 课程要求没有明确指出需要做出一个完整的管理系统,但鉴于该课程很多任务都要求做出一个GUI界面,且根据最终成绩来看,做出完整管理系统的同学得分较高,因此建议您做一个完整的管理系统,实现需要GUI实现的实验任务。
考核
- 实验报告50分。实验报告包括一些对课程文档中提供的Java代码进行修改的任务,其他则是编写GUI程序进行CRUD操作的任务。除展示代码效果外,还需要提供对出现的问题进行修复的过程。
- 考试50分。与《企业级数据库性能调优》不同的是,该课程是必修课,因此是正规的期末考试。
真题:2024.12.29
一、给出代码片段(5*15=75)
1.向一个表插入10万行,每500行提交一次
2.查询员工信息表,然后将奖金为0的员工绩效考核设置为不合格 提示:for update 与 where current of
3.键盘读入员工信息并插入,包含两个空列 使用setNull()
4.异常处理,给出两个SQLState代码,捕获异常并输出错误信息
5.数据库插入BLOB对象的照片
二、设计题(25)
1.12306如何实现查火车票不用登陆,买票需要登录考点:事务隔离性级别
2.实现一个12306火车信息查询,然后根据发时、票价等排序,根据席别等因素筛选
最终得分98分,课程的内容还是比较简单的,只要实验是自己做的,90+没问题。
试植梓为期 掔发为纾
——《韶华未既》
知识点总结
注意:以下内容较长
1.导入所需的包
import java.sql.*;
import java.io.*;
import java.lang.*;2.加载JDBC驱动
// MyJDBC.java
public class MyJDBC {
static { //内部静态类
try {
Class.forName("COM.ibm.db2.jdbc.app.DB2Driver");
} catch (Exception e) {
e.printStackTrace();
}
}
}3.实例化Connection对象
- 无用户名密码(本地账户登录)
try {
String url = "jdbc:db2:sample";
Connection conn = DriverManager.getConnection(url);
} catch (Exception e) {
throw new Exception("\nUsage: java MyJDBC [,uanme,passwd]");
}- 有用户名密码(远程登陆)
try {
String url = "jdbc:db2:sample";
String userid = "JLU";
String passwd = "826132";
Connection conn = DriverManager.getConnection(url, userid, passwd);
} catch (Exception e) {
throw new Exception("\nUsage: java MyJDBC [,uanme,passwd]");
}4.查询操作:Statement,ResultSet(注意同样要用try-catch包围,这里为了方便演示就省略了)
String sql = "SELECT EMPNO, LASTNAME FROM TEMPL WHERE SALARY > 40000";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
while (rs.next) {
System.out.println("empno = " + rs.getString(1) + "lastname = " + rs.getString(2));
}
rs.close();
stmt.close();
conn.close();5.更新操作:Statement VS PreparedStatement(注意同样要用try-catch包围,这里为了方便演示就省略了)
Statement
Statement stmt = conn.createStatement();
String sql = "UPDATE TEMPL SET LASTNAME = 'Stohl' WHERE EMPNO = '000110'";
int numRows = stmt.exectueUpdate();
System.out.println("Number of rows updated" + numRows);PreparedStatement
String sql = "UPDATE TEMPL SET SALARY = ? WHERE EMPNO = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, argv[1]);
pstmt.setString(2, argv[2]);
int numRows = pstmt.exectueUpdate();
System.out.println("Number of rows updated" + numRows);6.关闭自动提交
sample.setAutoCommit(false);7.最基本的GUI交互(GUI不作为考试重点)
- 输入对话框
String empno = JOptionPane.showInputDialog(null, "请输入员工工号");- 消息对话框
JOptionPane.showMessageDialog(null, "插入成功!");8.主变量与列变量
简单理解:主变量就是你编程用的语言定义的变量,列变量就是数据库里面的变量。
DB2工具DCLGEN:帮助找到列变量和主变量的对应关系。
db2dclgn -D sample -T employee -L Java意为找到sample数据库中employee表的各列在Java中的对应主变量类型。
9.空值处理
如果使用下列代码判断是否为空值:
String empno = rs.getString(1);
if (empno == null) {
//...
}当对应列变量为空值时,JDBC会根据变量类型为主变量赋予默认值,对于字符串类型,该值为空格。因此,空值处理的语句始终不会被执行。
JDBC提供wasNull()方法:
String empno = rs.getString(1);
if (rs.wasNull() {
//...
}当读取的列变量为空值,该方法返回true。
同样,如果我们需要给列变量赋空值,直接使用null将会给字符串赋值为'null'。JDBC提供setNull()方法:
pstmt.setNull(1, java.sql.Type.CHAR);
pstmt.setNull(2, java.sql.Type.SMALLINT);
pstmt.setNull(3, java.sql.Type.INTEGER);通过该方法即可设置空值。
10.异常处理(不必背诵错误代码,考试会给)
SQLCA:SQL通信区。高级语言调用SQL语句后,数据库引擎访问数据库后,用特殊代码向SQLCA返回访问状况。
数据库访问发生异常时,抛出SQLException异常。
SQLCode:错误代码集。在DB2中,<0代表SQL语句有错误,>0且!=100为警告信息,=0为返回0行(行不存在)。SQLCode依赖于具体数据库产品,由厂商自行定义,包含更加详细的错误信息。
SQLState:错误代码集。国际标准,所有数据库产品中多数代码是统一的。
例如:
try {
String sql = "DELETE FROM TEMPL WHERE EMPNO = 999";
pstmt = con.prepareStatement(sql);
deleteCount = pstmt.executeUpdate();
if ((SQLWarn = pstmt.getWarnings()) != null) {
System.out.println("\nWarning received on a DELETE \n");
}
} catch (SQLException e) {
if (e.getSQLState().equals("42818")) {
// 处理类型不兼容的异常
System.out.println("\n Operand data tyoes not Compatible \n");
} else {
System.out.println("\n Error deleting from TEMPL \n");
System.out.println("\n Value of SQLCode is: \n");
String SQLCode = e.getErrorCode(); //注意此处方法名是ErrorCode,而不是SQLCode
System.out.println(SQLCode);
}
}常见错误代码示例(转载自吉林大学数据库应用程序开发知识点总结-CSDN博客):
switch(SQLCODE) {
case 802:
//数据溢出
JOptionPane.showMessageDialog( null , "数据溢出" ,"SQL错误" , JOptionPane.ERROR_MESSAGE) ;
break;
case -007:
//SQL语句中有非法字符
JOptionPane.showMessageDialog( null , "SQL语句中有非法字符" ,"SQL信息" , JOptionPane.ERROR_MESSAGE) ;
break;
case -010:
//丢失引号
JOptionPane.showMessageDialog( null , "SQL语句丢失引号" ,"SQL信息" , JOptionPane.ERROR_MESSAGE) ;
break;
case -060:
//某特定数据类型的长度或者标量规范无效
JOptionPane.showMessageDialog( null , "某特定数据类型的长度或者标量规范无效" ,"SQL信息" , JOptionPane.ERROR_MESSAGE) ;
break;
case -102:
//字符串常量太长
JOptionPane.showMessageDialog( null , "字符串常量太长" ,"SQL信息" , JOptionPane.ERROR_MESSAGE) ;
break;
case -104:
//SQL语句中遇到非法符号
JOptionPane.showMessageDialog( null , "SQL语句中遇到非法符号" ,"SQL信息" , JOptionPane.ERROR_MESSAGE) ;
break;
case -433:
//指定的值太长
JOptionPane.showMessageDialog( null , "指定的值太长" ,"SQL信息" , JOptionPane.ERROR_MESSAGE) ;
break;
case -405:
//数值文字超出了范围
JOptionPane.showMessageDialog( null , "数值文字超出了范围" ,"SQL信息" , JOptionPane.ERROR_MESSAGE) ;
break;
}11.结果集游标
PreparedStatement pstmt = conn.prepareStatement(sql, rs.TYPE_FORWARD_ONLY, rs.CONCUR_READ_ONKY);第二个参数含义是只允许前向滚动,也就是说游标只能往前走,不能倒退,这是默认值。如果希望游标是可滚动的,可以改为rs.TYPE_SCROLL_INSENSITIVE(允许倒退,数据库变化时结果集不变)或者rs.TYPE_SCROLL_SENSITIVE(允许倒退,数据库变化时结果集同步)。
第三个参数限制了结果集的用途,意为只允许并发读取(只在读取的时候可以并发)。改为CONCUR_UPDATABLE则允许并发修改,但是本课程中DB2的驱动程序不支持。
对于Statement也同样适用,只需要把默认的无参方法改成重载的两个参数的方法即可。
Statement pstmt = conn.createStatement(rs.TYPE_FORWARD_ONLY, rs.CONCUR_READ_ONKY);物化:结果集暂存于磁盘。例如,需要对结果集排序时,内存中不足以保存排序结果,需要为结果集临时分配磁盘空间。
String mySelect = "SELECT LASTNAME, FIRSTNME FROM EMP FOR UPDATE";
String myUpdate = "UPDATE EMP SET FIRSTNAME = ? WHERE CURRENT OF ";这二者都不是标准的SQL语句。FOR UPDATE指示数据库引擎该语句将被用于修改,第二个字符串含有问号,含有参数标记,WHERE CURRENT OF后面等待游标名称,组合起来就是一个完整的SQL语句了。
String cursorName = null;
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(mySelect);
cursorName = rs.getCursorName();
PreparedStatement ps = conn.prepareStatement(myUpdate + cursorName);注意此处执行的是select语句,也就是查询。因此,cursorName返回的,也就是查询后得到的结果集的游标名。更新语句与这个游标相拼接,条件就是游标指向当前行的值。有没有发现这两条语句都是针对同一个表的?也就是说,查询结果集的游标定位到某行的时候,更新语句就直接可以用这个游标的指向来执行更新操作了。
while (rs.next) {
String lastname = rs.getString(1);
String firstnme = rs.getString(2);
if (lastname.equals("SMITH")) {
String newFirstnme = "George";
ps.setString(1, newFirstnme);
ps.executeUpdate();
}
}
rs.close();
ps.close();
stmt.close();
conn.close();这里随着select语句结果集游标的移动,逐个检查数据集中的姓氏是否为SMITH。当检查到姓SMITH的人时,update语句就可以直接通过游标定位要修改的行了。先查询——获取结果集——获取的结果集上进行修改。
rs.afterLast(); // 将结果集游标移动到最后一行之后
rs.prevoius(); // 游标前移一行
rs.absolute(1); // 移动到第一行
rs.absolute(-1); // 移动到最后一行
rs.first(); // 移动到第一行
rs.last(); // 移动到最后一行
rs.relative(2); // 前移两行
rs.relative(-2); //回退2行12.元数据接口
getColumnCount()结果集列数
getColumnLabel()结果集列的名称
DatabaseMetaData dbmd = sample.getMetaData();
ResultSet rs = dbmd.getSchemas();获取数据库的模式元数据
String[] tableTypes = {"TABLE", "VIEW"};
ResultSet rs = dbmd.getTables(null, "JLU", "%", tableTypes);获取模式为JLU的表信息
13.批处理
当执行了一行所有列的setString()后:
ps.addBatch();向批处理添加一行。
int[] rowCounts = ps.executeBatch();执行批处理,返回每个语句影响的行数。
14.大对象
BLOB:二进制大对象
CLOB:字符型大对象
SQL函数POSSTR(RESUME, 'Personal'):获取'Personal'字符串在RESUME列中第一次出现的位置。
SQL函数SUBSTR(RESUME, 1, 999):截取RESUME列中从1开始,读取999个字符。注意SQL的下标是1开始的。
SQL函数SUBSTR(RESUME, 114514):截取RESUME列中从下标为114514的字符开始,直到末尾的子串。
SQL select语句中,|| 不是逻辑运算符。它代表将前后两个字符串拼接起来。
读取CLOB时,要注意:select字句中截取后,还是CLOB;Java程序中要用Clob resumelob = rs.getClob()读取。
如何将Clob转化为我们常见的字符串呢?
while (rs.next()) {
empno = rs.getString(1);
resumefmt = rs.getString(2);
resumelob = rs.getClob(3);
long len = resumelob.length();
int len1 = (int)len;
String resumeout = resumelob.getSubString(1, len1);
}首先用resumelob.length()获取大对象的整体长度,强制转换为32位整型后,读取从第1位到结尾的字串,其实就是整个字符串,这样我们就得到了所需要的简历字符串。注意从结果集中读取CLOB时,不能使用String类型,也不能使用getString方法,要使用对应的Clob版本。
读取和插入BLOB,则需要使用Java流。
- 写入
File file = new File("C:/a.jpg");
java.io.BufferedInputStream imageInput = new java.io.BufferedInputStream(new java.io.FileInputStream(file));
pstmt.setBinaryStream(1, imageInput, (int)file.length());
pstmt.executeUpdate();首先获取文件对象,指定要写入的文件;然后使用该文件对象定义输入流;最后,将输入流写入数据库。
- 读取
读取同理,获得BLOB对象后,转换为流,定义文件对象,最后写入文件。
Blob blob = (Blob)rs.getBlob(1);
java.io.InputStream inputStream = blob.getBinaryStream();
File file = new File("C:/backa.jpg");
FileOutputStream fos = new FIleOutputStream(file);
int c;
while((c = inputStream.read()) != -1) {
fos.write(c);
}整体评价
跟往年的题不完全一样,还是要以PPT为主,学习通视频好好看看。
附录:关于8.3文件名(千问AI生成)
8.3文件名格式是一种早期的文件命名规范,主要应用于DOS和早期的Windows操作系统中。
“8.3”这个名字的含义:
- 8:代表文件名的主体部分(主名)最多只能有 8个字符。
- 3:代表文件的扩展名最多只能有 3个字符。
- 两者之间用一个点(
.)隔开,例如G9401.DBF。
为什么会有8.3格式? 这主要是为了兼容早期的DOS和FAT文件系统。随着Windows系统的发展,开始支持包含空格和特殊字符的长文件名。但为了保证旧版软件和系统的兼容性,Windows会自动为长文件名生成一个符合8.3规则的“短文件名”作为别名。
短文件名的生成规则: 当长文件名超过限制时,系统会按照以下规则自动生成短文件名:
- 取长文件名的前 6个有效字符(去除空格和特殊字符,并转换为大写)。
- 后面加上波浪号
~。 - 最后加上一个 数字(从1开始,用于避免文件名冲突)。
常见示例:
Program Files的短文件名通常是PROGRA~1。Program Files (x86)的短文件名通常是PROGRA~2。My Long File Name.txt可能会变成MYLONG~1.TXT。
在现代的Windows系统(如Windows 10/11)中,8.3格式已经不再是默认的命名方式,大家日常使用的都是长文件名。不过,系统底层依然保留了对它的支持,以确保某些老旧软件或特定脚本能够正常运行。