发出查询并处理结果

任何时候你想向数据库发出 SQL 语句,都需要一个 StatementPreparedStatement 实例。一旦你拥有一个 StatementPreparedStatement,你就可以使用它来发出查询。这将返回一个 ResultSet 实例,其中包含整个结果(有关如何更改此行为,请参见此处名为 基于游标获取结果 的部分)。示例 5.1,“在 JDBC 中处理简单查询” 说明了此过程。

此示例将发出一个简单的查询,并使用 Statement 打印出每一行的第一列。

Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM mytable WHERE columnfoo = 500");
while (rs.next()) {
    System.out.print("Column 1 returned ");
    System.out.println(rs.getString(1));
}
rs.close();
st.close();

此示例发出与之前相同的查询,但使用 PreparedStatement 和查询中的绑定值。

int foovalue = 500;
PreparedStatement st = conn.prepareStatement("SELECT * FROM mytable WHERE columnfoo = ?");
st.setInt(1, foovalue);
ResultSet rs = st.executeQuery();
while (rs.next()) {
    System.out.print("Column 1 returned ");
    System.out.println(rs.getString(1));
}
rs.close();
st.close();

默认情况下,驱动程序会一次性收集查询的所有结果。对于大型数据集来说,这可能很不方便,因此 JDBC 驱动程序提供了一种基于数据库游标的 ResultSet,并且只获取少量行。

少量行缓存在连接的客户端,当缓存用完时,通过重新定位游标来检索下一块行。

注意

基于游标的 ResultSets 并非在所有情况下都适用。有一些限制会导致驱动程序静默地回退到一次性获取整个 ResultSet

  • 与服务器的连接必须使用 V3 协议。这是 7.4 及更高版本服务器的默认设置(并且仅受支持)。

  • Connection 必须不在自动提交模式下。后端在事务结束时关闭游标,因此在自动提交模式下,后端会在从游标中获取任何内容之前关闭游标。

  • Statement 必须使用 ResultSet.TYPE_FORWARD_ONLYResultSet 类型创建。这是默认设置,因此不需要重写任何代码来利用此功能,但也意味着您不能向后滚动或在 ResultSet 中跳跃。

  • 给定的查询必须是一个单独的语句,而不是用分号连接在一起的多个语句。

将代码更改为使用游标模式与将 Statement 的获取大小设置为适当的大小一样简单。将获取大小设置为 0 将导致所有行被缓存(默认行为)。

// make sure autocommit is off
conn.setAutoCommit(false);
Statement st = conn.createStatement();

// Turn use of the cursor on.
st.setFetchSize(50);
ResultSet rs = st.executeQuery("SELECT * FROM mytable");
while (rs.next()) {
    System.out.print("a row was returned.");
}
rs.close();

// Turn the cursor off.
st.setFetchSize(0);
rs = st.executeQuery("SELECT * FROM mytable");
while (rs.next()) {
    System.out.print("many rows were returned.");
}
rs.close();

// Close the statement.
st.close();

使用 StatementPreparedStatement 接口时,必须考虑以下事项:

  • 您可以多次使用单个 Statement 实例。您可以在打开连接时创建一个,并在连接的整个生命周期中使用它。但是,您必须记住,在给定时间,每个 StatementPreparedStatement 只能存在一个 ResultSet

  • 如果您需要在处理 ResultSet 时执行查询,只需创建并使用另一个 Statement 即可。

  • 如果您正在使用线程,并且多个线程正在使用数据库,则必须为每个线程使用单独的 Statement。如果您考虑使用线程,请参考 在多线程或 Servlet 环境中使用驱动程序,因为它涵盖了一些重要事项。

  • 完成使用 StatementPreparedStatement 后,您应该关闭它。

  • 在 JDBC 中,问号 (?) 是 PreparedStatement 位置参数的占位符。但是,有一些 PostgreSQL® 运算符包含问号。为了防止将 SQL 语句中的这些问号解释为位置参数,请使用两个问号 ( ?? ) 作为转义序列。您也可以在 Statement 中使用此转义序列,但这不是必需的。具体来说,只有在 Statement 中,单个 ( ? ) 可以用作运算符。

使用ResultSet接口时,必须考虑以下事项

  • 在读取任何值之前,必须调用next()。这将返回一个布尔值,表示是否存在结果,但更重要的是,它会准备行以供处理。

  • 使用完ResultSet后,必须通过调用close()来关闭它。

  • 当使用Statement创建的ResultSet进行另一次查询时,当前打开的ResultSet实例将自动关闭。

  • 当使用PreparedStatement API时,ResultSet在五次查询执行后会切换到二进制模式(此默认值由prepareThreshold连接属性设置,请参阅服务器准备好的语句)。这可能会导致在调用某些方法时出现意外行为。例如,在执行次数超过设置的prepareThreshold后,对非字符串数据类型调用getString()等方法的结果,虽然在逻辑上是等效的,但其格式可能会有所不同,因为转换为对象方法会切换到返回类型与返回模式匹配的方法。

要更改数据(执行INSERTUPDATEDELETE),请使用executeUpdate()方法。此方法类似于用于发出SELECT语句的executeQuery()方法

但它不返回ResultSet,而是返回受INSERTUPDATEDELETE语句影响的行数。 示例 5.3,“在 JDBC 中删除行” 说明了用法。

此示例将发出一个简单的DELETE语句,并打印出删除的行数。

int foovalue = 500;
PreparedStatement st = conn.prepareStatement("DELETE FROM mytable WHERE columnfoo = ?");
st.setInt(1, foovalue);
int rowsDeleted = st.executeUpdate();
System.out.println(rowsDeleted + " rows deleted");
st.close();

要创建、修改或删除数据库对象(如表或视图),可以使用execute()方法。此方法类似于executeQuery()方法,但它不返回结果。 示例 5.4,“在 JDBC 中删除表” 说明了用法。

此示例将删除一个表。

Statement st = conn.createStatement();
st.execute("DROP TABLE mytable");
st.close();

PostgreSQL® JDBC 驱动程序使用 JDBC 4.2 实现对 Java 8 日期和时间 API(JSR-310) 的原生支持。

PostgreSQL® Java SE 8
DATE LocalDate
TIME [ WITHOUT TIME ZONE ] LocalTime
TIMESTAMP [ WITHOUT TIME ZONE ] LocalDateTime
TIMESTAMP WITH TIME ZONE OffsetDateTime

这与 JDBC 4.2 规范中的表 B-4 和 B-5 紧密相关。

注意

ZonedDateTimeInstantOffsetTime / TIME WITH TIME ZONE 不受支持。另请注意,所有 OffsetDateTime 实例都将以 UTC(偏移量为 0)表示。这是因为后端将它们存储为 UTC。

示例 5.2. 使用 JDBC 读取 Java 8 日期和时间值

Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM mytable WHERE columnfoo = 500");
while (rs.next()) {
    System.out.print("Column 1 returned ");
    LocalDate localDate = rs.getObject(1, LocalDate.class);
    System.out.println(localDate);
}
rs.close();
st.close();

对于其他数据类型,只需将其他类传递给 #getObject

注意

Java 数据类型需要与表 7.1 中的 SQL 数据类型匹配。

LocalDate localDate = LocalDate.now();
PreparedStatement st = conn.prepareStatement("INSERT INTO mytable (columnfoo) VALUES (?)");
st.setObject(1, localDate);
st.executeUpdate();
st.close();