发出查询和处理结果
每当要向数据库发出 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
实例。您可以在打开连接后创建它,并在连接的生存期内使用它。但你必须记住,在任何时间,每个Statement
或PreparedStatement
实例只有一个ResultSet
可以存在。 -
如果需要在处理
ResultSet
时执行查询,您可以简单地创建和使用另一个Statement
。 -
如果使用线程,并且多个线程正在使用数据库,则必须为每个线程使用单独的
Statement
。 如果您正在考虑使用线程,请参阅在多线程或 Servlet 环境中使用驱动程序,因为它涵盖了一些要点。 -
当您使用完
Statement
或PreparedStatement
后,您应该关闭它。 -
在 JDBC 中,问号 (
?
) 是PreparedStatement
的位置参数的占位符。 但是,有许多PostgreSQL运算符包含问号。为了防止 SQL 语句中的此类问号被解释为位置参数,可以使用两个问号 (??
) 作为转义序列。 您也可以在Statement
中使用此转义序列,但这不是必需的。具体来说,只有在Statement
中单个问号 (?
) 可以用作运算符。
使用ResultSet
接口时必须考虑以下几点:
-
在读取任何值之前,必须调用
next()
。如果有结果,则返回 true,但更重要的是,它会准备将要处理的行。 -
使用完
ResultSet
后,必须通过调用close()
来关闭它。 -
一旦您使用
Statement
进行另一个查询并创建ResultSet
,当前打开的ResultSet
实例会被自动关闭。 -
使用 PreparedStatement API 时,在执行五次查询后会切换到二进制模式(此默认值由连接属性
prepareThreshold
设置,请参阅服务端预备语句)。 这可能会导致调用某些方法时出现意外行为。例如,在非字符串数据类型的值上调用getString()
方法,即使在逻辑上等效,但在执行超过prepareThreshold
设置的次数后,当转换成对象的方法切换到返回类型与返回模式匹配的方法时,格式可能会有所不同。
若要更改数据(执行INSERT
、UPDATE
或DELETE
),请使用executeUpdate()
方法。此方法与executeQuery()
方法类似,executeQuery()
方法用于发出SELECT
语句,但它不返回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();
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-2 的描述非常一致。
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();