13.5 管理结果集 13.5.1 可滚动,可更新的结果集

13.5 管理结果集

JDBC使用ResultSet来封装执行查询得到的查询结果,然后通过移动ResultSet的记录指针来取出结果集的内容。除此之外,JDBC还允许通过ResultSet来更新记录,并提供了ResultSetMetaData来获得ResultSet对象的相关信息。

13.5.1 可滚动,可更新的结果集

前面提到, ResultSet定位记录指针的方法有absolute()previous()等方法,但前面程序自始至终都只用了next()方法来移动记录指针,实际上也可以使用absolute()previous()last()等方法来移动记录指针。

可以使用absolute()previous()afterLast()等方法自由移动记录指针的ResultSet()被称为可滚动的结果集。

Java 5以后 ResultSet 默认可滚动

JDK1.4以前,默认打开的ResultSet是不可滚动的,必须在创建StatementPreparedStatement时传入额外的参数。Java 5.0以后,默认打开的ResultSet就是可滚动的,无须传入额外的参数.

以默认方式打开的ResultSet是不可更新的,如果希望创建可更新的ResultSet,则必须在创建StatementPreparedStatement时传入额外的参数。

Connection在创建StatementPreparedStatement时还可额外传入如下两个参数。

  • resultSetType:控制ResultSet的类型,该参数可以取如下三个值。
    • ResultSet.TYPE_FOWARD_ONLY:该常量控制记录指针只能向前移动。这是JDK 1.4以前的默认值。
    • ResultSet.TYPE_SCROLL_INSENSITIVE:该常量控制记录指针可以自由移动(可滚动结果集),但底层数据的改变不会影响ResultSet的内容。
    • ResultSet.TYPE_SCROLL_SENSITIVE:该常量控制记录指针可以自由移动(可滚动结果集),而且底层数据的改变会影响ResultSet的内容
  • resultSetConcurrency:控制ResultSet并发类型,该参数可以接收如下两个值。
    • ResultSet.CONCUR_READ_ONLY:该常量指示ResultSet只读的并发模式(默认)。
    • ResultSet.CONCUR_UPDATABLE:该常量指示ResultSet可更新的并发模式。

注意:TYPE_SCROLL_INSENSITIVETYPE_SCROLL_SENSITIVE两个常量的作用需要底层数据库驱动的支持,对于有些数据库驱动来说,这两个常量并没有太大的区别。

下面代码通过这两个参数创建了一个PreparedStatement对象,由该对象生成的ResultSet对象将是可滚动、可更新的结果集。

需要指出的是,可更新的结果集还需要满足如下两个条件。

  • 所有数据都应该来自一个表。
  • 选出的数据集必须包含主键列。

通过该PreparedStatement创建的ResultSet就是可滚动、可更新的,程序可调用ResultSetupdateXxx(int columnIndex, Xxx value)方法来修改记录指针所指向的行的特定列的值,最后调用ResultSetupdateRow()方法来提交修改。
Java 8ResultSet添加了:

  • updateObject(String columnLabel,Object x, SQLType targetSqlType)
  • updateObject(int columnIndex, Object x, SQLType targetsqlType)

这两个默认方法,这两个方法可以直接用Object来修改记录指针所指记录、特定列的值,其中SQLType用于指定该数据列的类型.但目前最新的MySQL驱动暂不支持该方法。
下面程序示范了这种创建可滚动、可更新的结果集的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import java.util.*;
import java.io.*;
import java.sql.*;

public class ResultSetTest
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile) throws Exception
{
// 使用Properties类来加载属性文件
Properties props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
}
public void query(String sql) throws Exception
{
// 1.加载驱动
Class.forName(driver);
try (
// 2.获取数据库连接
Connection conn = DriverManager.getConnection(url, user, pass);
// 3.创建Statement对象
// 使用Connection来创建一个PreparedStatement对象
// 传入参数使得结果集可滚动,可更新.
PreparedStatement pstmt = conn.prepareStatement(sql,
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE);
// 4.使用Statement对象操作数据库
ResultSet rs = pstmt.executeQuery())
{
// 移动记录指针到最后一行
rs.last();
// 获取行号
int rowCount = rs.getRow();
for (int i = rowCount; i > 0; i--)
{
// 移动行记录指针到第i行
rs.absolute(i);
// 从结果集中取出数据列
System.out.println(rs.getString(1) + "\t" + rs.getString(2)
+ "\t" + rs.getString(3));
// 修改记录指针所有记录、第2列的值
rs.updateString(2, "学生名" + i);
// 提交修改
rs.updateRow();
}
}
}
public static void main(String[] args) throws Exception
{
ResultSetTest rt = new ResultSetTest();
rt.initParam("mysql.ini");
rt.query("select * from student_table");
}
}

运行上面程序,将会看到student_table表中的记录被倒过来输出了,因为是从最大记录行开始输出的。而且当程序运行结束后, student_table表中所有记录的student_name列的值都被修改了。
运行效果如下:

1
2
3
4
5
6
7
7       赵六    3
6 null 2
5 _王五 2
4 王五 2
3 李四 1
2 张三 1
1 张三 1

注意:
如果要创建可更新的结果集,则使用查询语句查询的数据通常只能来自于一个数据表,而且查询结果集中的数据列必须包含主键列,否则将会引起更新失败