在上一章中,我们介绍了JDBC查询数据的基本流程。在该流程中,一个重要的步骤是从Connection对象中创建Statement对象,并且通过这个Statement对象向数据库查询数据信息。除了查询,Statement对象还可以接收并处理数据更新,例如Insert语句、Update语句和Delete语句。
在使用ResultSet读取查询数据时,JDBC支持三种ResultSet。JDBC允许开发人员"向前移动"或者"向后移动"ResultSet对象中的"游标"(Cursor)。在查询过程中,如果在同一时刻数据库中的数据也发生了变化,JDBC还可以将新的数据变化体现在从ResultSet读取的数据中。
Statement类的成员方法executeQuery()用于数据查询。该方法接受一个SQL查询语句,返回一个ResultSet对象。开发人员可以通过ResultSet对象获取查询的结果。
在下面的示例中,我们查询student表中的所有学生信息。
import java.util.List;
import java.util.ArrayList;
import java.sql.*;
public class StudentDB {
public List<Student> fetchAll() throws Exception {
List<Student> allStudents = new ArrayList<Student>();
Class.forName("org.postgresql.Driver");
String url = "jdbc:postgresql://localhost/dbname";
String username = "littlewaterdrop";
String password = "password";
// 获取数据库连接
try (Connection connection = DriverManager.getConnection(url, username, password)) {
// 创建Statement对象
try (Statement stmt = connection.createStatement()) {
// 执行数据查询语句
ResultSet result = stmt.executeQuery("select * from student");
// 获取查询结果
while (result.next()) {
String name = result.getString("name");
Integer age = result.getInt("age");
String gender = result.getString("gender");
allStudents.add(new Student(name, age, gender));
}
}
}
return allStudents;
}
}
在使用get*()方法获取值的时候,开发人员需要根据列数据的类型选择使用相应的方法。下表列举了常用数据类型与方法的对应关系。
方法名称 | 数据类型 |
---|---|
getBoolean() | boolean |
getDate() | date |
getDouble() | numeric |
getFloat() | real |
getInt() | int |
getLong() | bigint |
getShort() | smallint |
getString() | char, varchar, text |
getTime() | time |
getTimestamp() | timestamp |
值得注意的是,在上面的例子中,我们使用了Connection类的成员方法createStatement()构造出Statement对象。当使用这个Statement对象执行查询语句,获得的ResultSet对象是TYPE_FORWARD_ONLY和CONCUR_READ_ONLY类型的。
这里涉及到两个概念。第一个是从ResultSet对象获取数据的方式。TYPE_FORWARD_ONLY是指该ResultSet只允许将游标"Cursor"向前移动来获取所有数据。所以,一旦游标"Cursor"向前移动之后,无法返回。ResultSet允许的另外两种访问方式是TYPE_SCROLL_INSENSITIVE和TYPE_SCROLL_SENSITIVE。TYPE_SCROLL_INSENSITIVE和TYPE_SCROLL_SENSITIVE是指ResultSet中的游标"Cursor"可以前后移动,也可以移动到一个指定的位置。也就是说游标可以自由移动(Scrollable)。INSENSITIVE和SENSITIVE的区别在于当在使用ResultSet获取数据时,如果此时数据在数据库中发生了变化,INSENSITIVE类型的ResultSet对象不会感知到数据的变化,而SENSITIVE类型的ResultSet能感知到。因此,TYPE_SCROLL_INSENSITIVE类型的ResultSet中的游标"Cursor"可以自由移动,但不能感知数据的变化。TYPE_SCROLL_SENSITIVE类型的ResultSet中的游标"Cursor"也可以自由移动,而且能感知数据的变化。
第二个概念是指ResultSet是否能改变当前访问的数据。CONCUR_READ_ONLY类型的ResultSet表示的是只读数据。查询得到的数据只能读,而不能写。CONCUR_UPDATABLE类型的ResultSet可以读写当前的数据。当用户修改了当前游标"Cursor"指向的记录时,该修改能够直接同步到数据库中。
在理解了上述的两个概念之后,开发人员可以通过createStatement()方法来创建出相应的Statement对象,并且在数据查询时获得相应类型的ResultSet。下面的示例创建了一个Statement对象。在运行查询语句之后,能返回TYPE_SCROLL_INSENSITIVE和CONCUR_READ_ONLY类型的ResultSet对象。
Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
所以,上述成员方法createStatement()能够创建出以下六种ResultSet对象。
ResultSet Type | ResultSet Concurrency |
---|---|
ResultSet.TYPE_FORWARD_ONLY | ResultSet.CONCUR_READ_ONLY |
ResultSet.TYPE_SCROLL_INSENSITIVE | ResultSet.CONCUR_READ_ONLY |
ResultSet.TYPE_SCROLL_SENSITIVE | ResultSet.CONCUR_READ_ONLY |
ResultSet.TYPE_FORWARD_ONLY | ResultSet.CONCUR_UPDATABLE |
ResultSet.TYPE_SCROLL_INSENSITIVE | ResultSet.CONCUR_UPDATABLE |
ResultSet.TYPE_SCROLL_SENSITIVE | ResultSet.CONCUR_UPDATABLE |
在默认情况下(使用无参数的Connection.createStatement()创建出的),ResultSet是TYPE_FORWARD_ONLY类型的。ResultSet内部的游标(Cursor)只能向前移动,每次只能移动一步。所以,最常见的、使用FORWARD_ONLY类型的ResultSet对象是使用While循环,每次调用成员方法next()向前移动一步。当到达最后位置后,next()方法会返回false。
在循环体中,ResultSet类的get*()方法允许访问当前位置的数据。
...
try (Statement stmt = connection.createStatement()) {
ResultSet result = stmt.executeQuery("select * from student");
while (result.next()) {
String name = result.getString("name");
Integer age = result.getInt("age");
String gender = result.getString("gender");
...
}
}
Scrollable类型的ResultSet允许开发人员任意设置当前的位置。例如,开发人员可以使用下面的方法设置当前游标(Cursor)的位置。
方法名 | 用途 |
---|---|
boolean absolute(int row) | 移动到指定的行。 |
void afterLast() | 移动到数据的最后位置,在最后一行数据的下一个位置。 |
void beforeFirst() | 移动到第一行数据前面的一个位置。 |
boolean first() | 移动到第一行数据的位置。 |
boolean last() | 移动到最后一行数据的位置。 |
boolean next() | 移动到下一行。 |
boolean previous() | 移动到上一行。 |
boolean relative(int rows) | 向前或者向后移动指定的行数。 |
当ResultSet对象的内部游标(Cursor)指向一行数据时,开发人员需要根据数据的类型,选择get*()成员方法获取相应的数据。下面的例子展示了如何获取布尔、整数、浮点数、字符串、数组(Array)、日期(Date)、时间戳(Timestamp)等数据类型的代码示例。
import java.sql.Array;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
...
ResultSet result = stmt.executeQuery("select * from student");
while (result.next()) {
Array anArrayByIndex = result.getArray(1); // the first column is 1
Array anArrayByName = result.getArray("an_array_field");
boolean anBooleanByIndex = result.getBoolean(1); // the first column is 1
boolean anBooleanByName = result.getBoolean("a_boolean_field");
byte aByteByIndex = result.getByte(1); // the first column is 1
byte aByteByName = result.getByte("a_byte_field");
byte[] aByteArrayByIndex = result.getBytes(1); // the first column is 1
byte[] aByteArrayByName = result.getBytes("a_byte_array_field");
Date aDateByIndex = result.getDate(1); // the first column is 1
Date aDateByName = result.getDate("a_date_field");
double aDoubleByIndex = result.getDouble(1); // the first column is 1
double aDoubleByName = result.getDouble("a_double_field");
float aFloatByIndex = result.getFloat(1); // the first column is 1
float aFloatByName = result.getFloat("a_float_field");
int aIntByIndex = result.getInt(1); // the first column is 1
int aIntByName = result.getInt("a_int_field");
long aLongByIndex = result.getLong(1); // the first column is 1
long aLongByName = result.getLong("a_long_field");
short aShortByIndex = result.getShort(1); // the first column is 1
short aShortByName = result.getShort("a_short_field");
String aStringByIndex = result.getString(1); // the first column is 1
String aStringByName = result.getString("a_string_field");
Time aTimeByIndex = result.getTime(1); // the first column is 1
Time aTimeByName = result.getTime("a_time_field");
Timestamp aTimestampByIndex = result.getTimestamp(1); // the first column is 1
Timestamp aTimestampByName = result.getTimestamp("a_timestamp_field");
...
}
当使用CONCUR_READ_ONLY类型的ResultSet对象时,我们只能读取和访问数据。但是,当使用CONCUR_UPDATABLE类型的ResultSet对象时,我们还可以更新数据。ResultSet类还提供了update*()成员方法用于更新数据。被更新的数据会直接写入数据库中。
例如,当过了一年之后,我们需要将所有学生的年龄增加1。在下面的代码中,我们创建了CONCUR_UPDATABLE类型的Statement。因此,创建出来的ResultSet是Updatable的。在循环中,我们先获得年龄;自增1之后,再通过updateInt()方法设置新的年龄。
...
try (Statement stmt = connection.createStatement(ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_UPDATABLE)) {
ResultSet result = stmt.executeQuery("select * from student");
while (result.next()) {
int age = result.getInt("age");
age++;
result.updateInt("age", age);
}
}
和get*()方法类似,update*()方法也允许使用序号和名称来设置数据的值。使用的方法应与数据类型相匹配。
Statement类提供成员方法executeUpdate()方法来执行Insert、Update和Delete语句。数据定义语句(Data Definition Language),例如:Create、Drop、Alter等语句,也可以通过executeUpdate()方法执行。executeUpdate()方法返回受到影响的行的个数(The number of rows affected)。
Statement类的execute()方法可以运行任何类型的SQL语句,包括executeQuery()和executeUpdate()运行的语句。execute()方法返回一个布尔值。True表示第一个结果是一个ResultSet对象。
在下面的例子中,我们插入了一个新的学生记录(David),更新了学生Amy的年龄,删除了学生James的记录。
...
try (Statement stmt = connection.createStatement()) {
// 插入新生David的记录
stmt.executeUpdate("insert into student values ('David', 19, 'male')");
// 更新学生Amy的年龄
stmt.executeUpdate("update student set age = 21 where name = 'Amy'");
// 删除学生James的记录
stmt.executeUpdate("delete from student where name = 'James'");
}
Statement接口还允许开发人员一次性运行大量的SQL语句。我们称这种运行方式为批处理(Batch Processing)。Statement接口使用addBatch()方法和executeBatch()方法来支持批处理方式运行SQL语句。addBatch()方法向Statement对象新增一条SQL语句;executeBatch()方法会一次性执行这些SQL语句。这称为一次批量处理(a batch)。
executeBatch()方法返回一个整数数组,表示每一条SQL语句执行的结果。数组中的结果顺序与添加SQL语句的顺序保持一致。
如下面的代码所示,我们首先向Statement对象添加了三条语句,并在最后调用executeBatch()方法一次性运行它们。
...
try (Statement stmt = connection.createStatement()) {
// 插入新生David的记录
stmt.addBatch("insert into student values ('David', 19, 'male')");
// 更新学生Amy的年龄
stmt.addBatch("update student set age = 21 where name = 'Amy'");
// 删除学生James的记录
stmt.addBatch("delete from student where name = 'James'");
int[] results = stmt.executeBatch();
}
本章介绍了如何使用JDBC接口查询、增加、更新和删除数据库中数据的方法。当查询数据时,开发人员可以通过创建不同类型的Statement来获得相应类型的ResultSet。FORWARD_ONLY类型的ResultSet只能向前移动内部的游标(Cursor)。Scrollable类型的ResultSet能够自由来回地移动游标(Cursor)。Updatable类型的ResultSet允许在读取数据时,同时修改数据,并将数据直接写入数据库中。我们在接下来的章节中继续介绍PreparedStatement的使用方法。
注册用户登陆后可留言