发出查询并处理结果
任何时候你想向数据库发出 SQL 语句,都需要一个 Statement
或 PreparedStatement
实例。一旦你拥有一个 Statement
或 PreparedStatement
,你就可以使用它来发出查询。这将返回一个 ResultSet
实例,其中包含整个结果(有关如何更改此行为,请参见此处名为 基于游标获取结果 的部分)。示例 5.1,“在 JDBC 中处理简单查询” 说明了此过程。
示例 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_ONLY
的ResultSet
类型创建。这是默认设置,因此不需要重写任何代码来利用此功能,但也意味着您不能向后滚动或在ResultSet
中跳跃。给定的查询必须是一个单独的语句,而不是用分号连接在一起的多个语句。
示例 5.2. 设置获取大小以打开和关闭游标。
将代码更改为使用游标模式与将 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();
使用 Statement 或 PreparedStatement 接口
使用 Statement
或 PreparedStatement
接口时,必须考虑以下事项:
-
您可以多次使用单个
Statement
实例。您可以在打开连接时创建一个,并在连接的整个生命周期中使用它。但是,您必须记住,在给定时间,每个Statement
或PreparedStatement
只能存在一个ResultSet
。 -
如果您需要在处理
ResultSet
时执行查询,只需创建并使用另一个Statement
即可。 -
如果您正在使用线程,并且多个线程正在使用数据库,则必须为每个线程使用单独的
Statement
。如果您考虑使用线程,请参考 在多线程或 Servlet 环境中使用驱动程序,因为它涵盖了一些重要事项。 -
完成使用
Statement
或PreparedStatement
后,您应该关闭它。 -
在 JDBC 中,问号 (
?
) 是PreparedStatement
位置参数的占位符。但是,有一些 PostgreSQL® 运算符包含问号。为了防止将 SQL 语句中的这些问号解释为位置参数,请使用两个问号 (??
) 作为转义序列。您也可以在Statement
中使用此转义序列,但这不是必需的。具体来说,只有在Statement
中,单个 (?
) 可以用作运算符。
使用 ResultSet 接口
使用ResultSet
接口时,必须考虑以下事项
-
在读取任何值之前,必须调用
next()
。这将返回一个布尔值,表示是否存在结果,但更重要的是,它会准备行以供处理。 -
使用完
ResultSet
后,必须通过调用close()
来关闭它。 -
当使用
Statement
创建的ResultSet
进行另一次查询时,当前打开的ResultSet
实例将自动关闭。 -
当使用PreparedStatement API时,
ResultSet
在五次查询执行后会切换到二进制模式(此默认值由prepareThreshold
连接属性设置,请参阅服务器准备好的语句)。这可能会导致在调用某些方法时出现意外行为。例如,在执行次数超过设置的prepareThreshold
后,对非字符串数据类型调用getString()
等方法的结果,虽然在逻辑上是等效的,但其格式可能会有所不同,因为转换为对象方法会切换到返回类型与返回模式匹配的方法。
执行更新
要更改数据(执行INSERT
、UPDATE
或DELETE
),请使用executeUpdate()
方法。此方法类似于用于发出SELECT
语句的executeQuery()
方法
但它不返回ResultSet
,而是返回受INSERT
、UPDATE
或DELETE
语句影响的行数。 示例 5.3,“在 JDBC 中删除行” 说明了用法。
示例 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 中删除表” 说明了用法。
示例 5.4. 在 JDBC 中删除表
此示例将删除一个表。
Statement st = conn.createStatement();
st.execute("DROP TABLE mytable");
st.close();
使用 Java 8 日期和时间类
PostgreSQL® JDBC 驱动程序使用 JDBC 4.2 实现对 Java 8 日期和时间 API(JSR-310) 的原生支持。
表 5.1. 支持的 Java 8 日期和时间类
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 紧密相关。
注意
ZonedDateTime
、Instant
和OffsetTime / 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 数据类型匹配。
示例 5.3. 使用 JDBC 写入 Java 8 日期和时间值
LocalDate localDate = LocalDate.now();
PreparedStatement st = conn.prepareStatement("INSERT INTO mytable (columnfoo) VALUES (?)");
st.setObject(1, localDate);
st.executeUpdate();
st.close();