发出查询和处理结果
每当要向数据库发出 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();