jdbc-3

第五十九章 Statement接口的基本使用方法

1. 简介

在上一章中,我们介绍了JDBC查询数据的基本流程。在该流程中,一个重要的步骤是从Connection对象中创建Statement对象,并且通过这个Statement对象向数据库查询数据信息。除了查询,Statement对象还可以接收并处理数据更新,例如Insert语句、Update语句和Delete语句。

在使用ResultSet读取查询数据时,JDBC支持三种ResultSet。JDBC允许开发人员"向前移动"或者"向后移动"ResultSet对象中的"游标"(Cursor)。在查询过程中,如果在同一时刻数据库中的数据也发生了变化,JDBC还可以将新的数据变化体现在从ResultSet读取的数据中。

2. 数据查询的使用方法

2.1 基本方法

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

2.2 ResultSet对象

值得注意的是,在上面的例子中,我们使用了Connection类的成员方法createStatement()构造出Statement对象。当使用这个Statement对象执行查询语句,获得的ResultSet对象是TYPE_FORWARD_ONLYCONCUR_READ_ONLY类型的。

这里涉及到两个概念。第一个是从ResultSet对象获取数据的方式。TYPE_FORWARD_ONLY是指该ResultSet只允许将游标"Cursor"向前移动来获取所有数据。所以,一旦游标"Cursor"向前移动之后,无法返回。ResultSet允许的另外两种访问方式是TYPE_SCROLL_INSENSITIVETYPE_SCROLL_SENSITIVETYPE_SCROLL_INSENSITIVETYPE_SCROLL_SENSITIVE是指ResultSet中的游标"Cursor"可以前后移动,也可以移动到一个指定的位置。也就是说游标可以自由移动(Scrollable)。INSENSITIVESENSITIVE的区别在于当在使用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_INSENSITIVECONCUR_READ_ONLY类型的ResultSet对象。

Statement stmt = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);

所以,上述成员方法createStatement()能够创建出以下六种ResultSet对象。

ResultSet TypeResultSet Concurrency
ResultSet.TYPE_FORWARD_ONLYResultSet.CONCUR_READ_ONLY
ResultSet.TYPE_SCROLL_INSENSITIVEResultSet.CONCUR_READ_ONLY
ResultSet.TYPE_SCROLL_SENSITIVEResultSet.CONCUR_READ_ONLY
ResultSet.TYPE_FORWARD_ONLYResultSet.CONCUR_UPDATABLE
ResultSet.TYPE_SCROLL_INSENSITIVEResultSet.CONCUR_UPDATABLE
ResultSet.TYPE_SCROLL_SENSITIVEResultSet.CONCUR_UPDATABLE

2.3 ResultSet中的游标(Cursor)操作

在默认情况下(使用无参数的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");
    ...
}

2.4 ResultSet中的数据更新操作

当使用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*()方法也允许使用序号和名称来设置数据的值。使用的方法应与数据类型相匹配。

3. 数据的增、删、改操作

Statement类提供成员方法executeUpdate()方法来执行InsertUpdateDelete语句。数据定义语句(Data Definition Language),例如:CreateDropAlter等语句,也可以通过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'");
}

4. 批处理

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();
}

5. 小结

本章介绍了如何使用JDBC接口查询、增加、更新和删除数据库中数据的方法。当查询数据时,开发人员可以通过创建不同类型的Statement来获得相应类型的ResultSet。FORWARD_ONLY类型的ResultSet只能向前移动内部的游标(Cursor)。Scrollable类型的ResultSet能够自由来回地移动游标(Cursor)。Updatable类型的ResultSet允许在读取数据时,同时修改数据,并将数据直接写入数据库中。我们在接下来的章节中继续介绍PreparedStatement的使用方法。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.