Java 数据持久化系列(二)JDBC API 规范总结
总览
首先,来总览下 JDBC API:
JDBC API 规范
JDBC API 作为 Java SE™(Java 标准版)的一部分,由以下部分组成:
JDBC 核心 API ——
java.sql
package。JDBC 可选 API ——
javax.sql
package,是 Java EE™(Java 企业版)的重要组成部分。
其中,java.sql
package 包含下列 API:
通过
java.sql.DriverManager
与数据库建立连接java.sql.DriverManager
类 - 用于与驱动程序建立连接java.sql.SQLPermission
类java.sql.Driver
接口 - 提供用于注册和连接驱动程序的 API。java.sql.DriverPropertyInfo
类 - 提供 JDBC 驱动程序的属性。
发送 SQL 语句到数据库
java.sql.Connection
接口 - 提供创建语句、管理连接及其属性的方法java.sql.Statement
接口 - 用于发送基本的 SQL 语句java.sql.PreparedStatement
接口 - 用于发送预编译语句或基本 SQL 语句(继承自Statement
)java.sql.CallableStatement
接口 - 用于调用数据库存储过程(继承自PreparedStatement
)java.sql.Savepoint
接口 - 在事务中提供保存点
检索和更新查询结果
java.sql.ResultSet
接口
标准映射(SQL 数据类型到 Java 类或接口)
自定义映射(SQL user-defined type (UDT) 到 Java 类)
元数据
java.sql.DatabaseMetaData
接口 - 提供有关数据库的信息java.sql.ResultSetMetaData
接口 - 提供有关ResultSet
对象的列信息java.sql.ParameterMetaData
接口 - 提供有关PreparedStatement
命令的参数信息
异常
java.sql.SQLException
类 - 被大多数方法抛出,当数据访问出现问题或出于其它原因java.sql.SQLWarning
类 - 抛出表示警告java.sql.DataTruncation
类 - 抛出表示数据可能已被截断java.sql.BatchUpdateException
类 - 抛出表示批量更新中的部分命令未执行成功
下面重点看下常用的接口和类。
DriverManager 类
java.sql.DriverManager
类充当用户和驱动程序之间的接口。它跟踪可用的驱动程序并处理数据库与相应驱动程序之间的连接。DriverManager
类维护了一个通过调用 DriverManager.registerDriver()
方法来注册自己的 java.sql.Driver
类列表。
常用方法:
1 | static void registerDriver(Driver driver) // 用于通过 `DriverManager` 注册给定的驱动程序。 |
关于 Driver 驱动程序注册,详见《注册驱动程序》。
Connection 接口
java.sql.Connection
接口表示 Java 应用程序和数据库之间的会话(Session),它提供了许多事务管理方法如:
1 | void setAutoCommit(boolean status) // 修改当前 `Connection` 对象的事务自动提交模式。默认为 `true`。 |
Connection
接口同时也是一个工厂类,用于获取 Statement
、PreparedStatement
和 DatabaseMetaData
对象:
1 | Statement createStatement(...) // 创建一个可用于执行 SQL 查询或更新的语句对象。 |
Statement 接口
java.sql.Statement
接口提供用于执行数据库查询与更新的方法。Statement
接口是 ResultSet
的工厂,即它提供工厂方法来获取 ResultSet
的对象。
1 | ResultSet executeQuery(String sql) // 用于执行 `SELECT` 查询并返回 `ResultSet` 的对象。 |
批处理
除了通过上述方法来执行单个查询或更新,还可以通过下列方法执行批量命令:
1 | void addBatch(String sql) |
使用批量命令前,记得先使用 setAutoCommit()
将事务的自动提交模式设置为 false
。
批处理允许您将相关的 SQL 语句分组到批处理中,并通过一次调用数据库来提交它们。当您一次性向数据库发送多个 SQL 语句时,可以减少通信开销,从而提高性能。参考:JDBC - Batch Processing。
PreparedStatement 接口
java.sql.PreparedStatement
接口是 java.sql.Statement
的子接口。它用于执行参数化查询(parameterized query),例如:
1 | PreparedStatement ps = connection.prepareStatement("insert into emp values(?, ?, ?)"); |
为什么要使用 PreparedStatement
?
- 提升性能:应用程序的性能会更快,因为 SQL 语句只会编译一次。
- 提升安全:预防 SQL 注入
创建预编译的参数化查询语句后,需要通过 setXxx
方法设置对应参数。参数设置完毕后,就可以通过下列方法执行 SQL 语句:
1 | ResultSet executeQuery() // 用于执行 `SELECT` 查询并返回 `ResultSet` 的对象。 |
主键回写
java.sql.Connection
创建 java.sql.PreparedStatement
时,允许通过 autoGeneratedKeys
指定是否返回自增主键:
1 | PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException; |
autoGeneratedKeys
- a flag indicating whether auto-generated keys should be returned; one ofStatement.RETURN_GENERATED_KEYS
orStatement.NO_GENERATED_KEYS
1 | /** |
例子:
1 | // 构造 PreparedStatement 时,多了第二个参数,指定了需要主键回写 |
批处理
PreparedStatement
还提供了批处理方式,减少网络请求,提升性能,API 如下:
1 | /** |
未使用批处理方法:
1 | PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)"); |
使用批处理方法,一次性执行多条 SQL:
1 | PreparedStatement ps = conn.prepareStatement("INSERT into employees values (?, ?, ?)"); |
ResultSet 接口
java.sql.ResultSet
对象维护了一个指向 table 行的游标。游标初始值指向第 0 行。默认情况下,ResultSet
对象只能向前移动,并且不可更新。可以通过在 createStatement(int, int)
方法中传递指定参数修改该默认行为。
可以通过以下方法操作游标:
1 | boolean next() // 将游标移动到当前位置的下一行。 |
将游标移动到指定行之后,可以通过 getXxx
方法获取当前行的指定列的数据。
此外,还可以直接获取 table 的元数据,例如列的总数,列名,列类型等:
1 | ResultSetMetaData getMetaData() |
ResultSetMetaData 接口
java.sql.ResultSetMetaData
用于获取 table 的元数据,例如列的总数,列名,列类型等。
DatabaseMetaData 接口
java.sql.DatabaseMetaData
用于获取数据库的元数据,例如数据库产品名称,数据库产品版本,驱动程序名称,表总数名称,总视图名称等。
RowSet 接口
javax.sql.RowSet
继承自 java.sql.ResultSet
,是其包装器类。它包含类似 ResultSet
的表格数据,但使用起来非常简单灵活。其实现类如下:
下面是一个不含事件处理代码的 JdbcRowSet
的简单示例:
1 | try (JdbcRowSet rowSet = RowSetProvider.newFactory().createJdbcRowSet()) { |
对比下面传统的 JDBC API,代码更加直观,需要直接管理的资源也更少:
1 | try (Connection conn = DriverManager.getConnection(url)) { |
要使用 JdbcRowSet
执行事件处理,需要在 JdbcRowSet
的 addRowSetListener
方法中添加 RowSetListener
的实例。RowSetListener
接口提供了必须实现的三个方法,如下:
1 | void cursorMoved(RowSetEvent event); |
DataSource 接口
JDBC API 示例
JDBC API 的使用步骤如下:
其中:
- 步骤一:JDBC API 从 4.0 开始利用 Java SPI 机制自动加载驱动程序,可以省略该步骤。
- 步骤二、三:如果使用如 Spring
JdbcTempate
、MyBatis 等框架,可以省略该步骤。 - 步骤五:使用
try-with-resources
语句,可以省略该步骤。
下面来两个示例:
存储图片
下例通过 PreparedStatement
接口的 setBinaryStream()
方法将图片(二进制信息)存储到数据库中。为了将图片存储到数据库中,需要在表中使用 BLOB
(Binary Large Object)数据类型。
1 | try (Connection conn = DriverManager.getConnection(url)) { |
注意:这只是一个例子,生产环境中是不会将这类二进制信息存储到数据库中的,而是存储到专门的文件系统,以提升性能,并节省宝贵的数据库资源 :)
检索图片
Using try-with-resources
Statements to Automatically Close JDBC Resources:
1 | try (Connection conn = DriverManager.getConnection(url)) { |
JDBC 4.1 (Java SE 7) introduces the ability to use a try-with-resources
statement to automatically close java.sql.Connection
, java.sql.Statement
, and java.sql.ResultSet
objects, regardless of whether a SQLException
or any other exception has been thrown. See The try-with-resources Statement for more information.
参考
《Getting Started with the JDBC API》
https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/jdbc_41.html
https://docs.oracle.com/javase/8/docs/technotes/guides/jdbc/jdbc_42.html
https://docs.oracle.com/javase/9/docs/api/java/sql/package-summary.html
https://www.javatpoint.com/java-jdbc