什么是 JDBC
JDBC 全称 Java DataBase Connection,是 Java 定义的一套访问关系型数据库的规范,实现由各数据库厂商来完成,通过这套 API,Java 开发者可以轻松的访问各数据库。
如何通过 JDBC 访问数据库
通过 JDBC 访问数据库有一套固定的模板,查询数据库的示例代码如下。
String url = "jdbc:mysql://127.0.0.1:3306/test"; String username = "root"; String password = "12345678"; // 1. 获取连接 Connection connection = DriverManager.getConnection(url, username, password); // 2. 获取查询语句 PreparedStatement preparedStatement = connection.prepareStatement("select * from test"); // 3. 执行查询,获取结果集 ResultSet resultSet = preparedStatement.executeQuery(); while (resultSet.next()){ // 4. 从结果集取数据 int id = resultSet.getInt("id"); System.out.println(id); } // 5. 关闭连接 resultSet.close(); preparedStatement.close(); connection.close();
上述代码中涉及到了一些 JDBC 相关的 API,下面加以介绍。
JDBC 提供了哪些 API 抽象?
驱动
为了操作数据库,我们需要先获取一个数据库的连接,驱动的作用正是获取某一个数据库的连接,JDBC 支持一个程序中同时存在多个驱动,驱动在 JDBC 中对应的接口是java.sql.Driver,驱动由驱动管理器java.sql.DriverManager进行管理,下面进行单独介绍。
java.sql.Driver
Driver 作为一个接口,由具体的数据库厂商进行实现,在加载驱动时,驱动将创建自己的实例并向驱动管理器进行注册,因此我们经常看到一些类似Class.forName("com.mysql.cj.jdbc.Driver")的代码。以 MySQL 为例,实现为com.mysql.cj.jdbc.Driver,代码如下。
public class Driver extends NonRegisteringDriver implements java.sql.Driver { static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } public Driver() throws SQLException { } }
MySQL 的驱动在静态代码块中添加了向驱动管理器注册自身的逻辑,因此调用Class.forName("com.mysql.cj.jdbc.Driver")方法后驱动管理器就对 MySQL 的驱动进行管理。
下面为 Driver 接口中的一些相对来说比较重要的方法,分别加以介绍。
Driver 获取 Connection 的示例代码如下。
String url = "jdbc:mysql://127.0.0.1:3306/test"; String username = "root"; String password = "12345678"; Driver driver = new com.mysql.cj.jdbc.Driver(); Properties properties = new Properties(); properties.put("user", username); properties.put("password", password); Connection connection = driver.connect(url, properties); connection.close();
java.sql.DriverManager
DriverManager 是 Driver 的管理器,提供了注册驱动、取消注册驱动、获取连接等方法,有了 DriverManager 之后,我们每次获取连接时就不用再关心使用哪个具体的 Driver。先看 DriverManager 提供的主要方法。
从上面的方法中可以看到,DriverManager 的作用就是管理 Driver,进一步抽象 Connection 的获取,DriverManager 会从已注册的驱动中尝试获取 Connection。如果只是提供 Driver 的注册和取消注册方法,那么使用 DriverManager 获取 Connection 前,使用方仍然要关心 Driver 的具体实现,为了解决这个问题,DriverManager 初始化时会尝试初始化 Driver,具体有两种方式,感兴趣的小伙伴可自行阅读源码。
初始化 jdbc.dirvers 系统属性指定的 Driver,多个 Driver 使用:分割。
JDBC 4 开始利用 SPI 机制初始化 Driver。关于 SPI 机制,可参考我前面的文章 《Java 基础知识之 SPI》
有了 SPI 机制之后,我们只需要引入具体数据库的驱动即可,而不用再调用Class.forName("com.mysql.cj.jdbc.Driver")方法。MySQL 的驱动目前已支持 SPI 。
最后,再上一段 DriverManager 获取 Connection 的示例代码。
// Class.forName("com.mysql.cj.jdbc.Driver") 不再需要调用该方法初始化驱动 String url = "jdbc:mysql://127.0.0.1:3306/test"; String username = "root"; String password = "12345678"; Connection connection = DriverManager.getConnection(url, username,password); connection.close();
数据源
不管是 Driver 还是 DriverManager,最终都要获取连接,JDBC 2.0 开始,提出一个数据源的概念,也是用于获取连接,其接口为javax.sql.DataSource
,其上只定义两个获取连接的方法,具体如下。
public interface DataSource extends CommonDataSource, Wrapper { Connection getConnection() throws SQLException; Connection getConnection(String username, String password) throws SQLException; }
目前有两个使用较多的数据源,分别是 HiKariCP 和 Druid 。感兴趣的小伙伴可自行研究。
连接
连接在 JDBC API 的抽象是java.sql.Connection,表示客户端和某一种具体数据库之间的会话,连接提供的 API 大概可分为如下几类。
1. 获取 Statement 的 API
Statement 用于执行 SQL,后面会进行介绍,这里先看有哪些获取 Statement 的方法。
/** * 创建一个可以生成给定类型、并发性、保持性的 ResultSet 的 Statement * 默认的结果集类型是 ResultSet.TYPE_FORWARD_ONLY, * 默认的并发性是 ResultSet.CONCUR_READ_ONLY, * 默认的保持性可以调用 #getHoldability 方法获取 * * @param resultSetType 结果集类型,可取值为 ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE、ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency 结果集的并发性,可取值为 ResultSet.CONCUR_READ_ONLY,ResultSet.CONCUR_UPDATABLE * @param resultSetHoldability 结果集的保持性,可取的值为 ResultSet.HOLD_CURSORS_OVER_COMMIT、ResultSet.CLOSE_CURSORS_AT_COMMIT */ Statement createStatement() throws SQLException; Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException; Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException; /** * 创建 PreparedStatement * 默认的结果集类型是 ResultSet.TYPE_FORWARD_ONLY, * 默认的并发性是 ResultSet.CONCUR_READ_ONLY, * 默认的保持性可以调用 #getHoldability 方法获取 * * @param sql 要发送到数据库的 SQL ,可以包含多个 ? 作为参数 * @param resultSetType 结果集类型,可取值为 ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE、ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency 结果集的并发性,可取值为 ResultSet.CONCUR_READ_ONLY,ResultSet.CONCUR_UPDATABLE * @param resultSetHoldability 结果集的保持性,可取的值为 ResultSet.HOLD_CURSORS_OVER_COMMIT、ResultSet.CLOSE_CURSORS_AT_COMMIT * @param autoGeneratedKeys 返回的 PreparedStatement 是否可以获取自动生成的键,可取值为 Statement.RETURN_GENERATED_KEYS、Statement.NO_GENERATED_KEYS * @param columnIndexes 目标表中列的索引,指示返回的 PreparedStatement 可以获取到自动生成的哪些键 * @param columnNames 目标表中的列名,指示返回的 PreparedStatement 可以获取到自动生成的哪些键 */ PreparedStatement prepareStatement(String sql)throws SQLException; PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException; PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException; PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException; PreparedStatement prepareStatement(String sql, int columnIndexes[]) throws SQLException; PreparedStatement prepareStatement(String sql, String columnNames[]) throws SQLException; /** * 创建一个可以生成给定类型、并发性、保持性的 ResultSet 的 CallableStatement * 默认的结果集类型是 ResultSet.TYPE_FORWARD_ONLY, * 默认的并发性是 ResultSet.CONCUR_READ_ONLY, * 默认的保持性可以调用 #getHoldability 方法获取 * * @param sql 要发送到数据库的 SQL ,可以包含多个 ? 作为参数 * @param resultSetType 结果集类型,可取值为 ResultSet.TYPE_FORWARD_ONLY、ResultSet.TYPE_SCROLL_INSENSITIVE、ResultSet.TYPE_SCROLL_SENSITIVE * @param resultSetConcurrency 结果集的并发性,可取值为 ResultSet.CONCUR_READ_ONLY,ResultSet.CONCUR_UPDATABLE * @param resultSetHoldability 结果集的保持性,可取的值为 ResultSet.HOLD_CURSORS_OVER_COMMIT、ResultSet.CLOSE_CURSORS_AT_COMMIT */ CallableStatement prepareCall(String sql) throws SQLException; CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException; CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException;
2. 事务相关的 API
// 事务隔离级别:不支持事务隔离级别 int TRANSACTION_NONE = 0; // 事务隔离级别:读未提交 int TRANSACTION_READ_UNCOMMITTED = 1; // 事务隔离级别:读已提交 int TRANSACTION_READ_COMMITTED = 2; // 事务隔离级别:可重复度 int TRANSACTION_REPEATABLE_READ = 4; // 事务隔离级别:序列号 int TRANSACTION_SERIALIZABLE = 8; // 设置事务隔离级别 void setTransactionIsolation(int level) throws SQLException; // 获取事务隔离级别 int getTransactionIsolation() throws SQLException; // 设置是否自动提交事务,默认情况执行完一条 SQL 后自动提交事务 void setAutoCommit(boolean autoCommit) throws SQLException; // 获取是否自动提交事务 boolean getAutoCommit() throws SQLException; // 提交事务 void commit() throws SQLException; // 回滚事务 void rollback() throws SQLException; // 设置事务的保存点,在事务内创建 rollback 将返回到该保存点,事务外创建新的事务将从该保存点开始 Savepoint setSavepoint() throws SQLException; Savepoint setSavepoint(String name) throws SQLException;
3. 创建 JDBC 数据类型实例的 API
JDBC 中的数据类型并非每个关系型数据库都支持,数据类型后面加以介绍,Connection 可以创建的数据类型如下。
Clob createClob() throws SQLException; Blob createBlob() throws SQLException; NClob createNClob() throws SQLException; SQLXML createSQLXML() throws SQLException; Array createArrayOf(String typeName, Object[] elements) throws SQLException; Struct createStruct(String typeName, Object[] attributes) throws SQLException;
4. 其他 API
// 获取数据库的元数据 DatabaseMetaData getMetaData() throws SQLException; // 设置/获取客户端的信息 void setClientInfo(Properties properties) throws SQLClientInfoException; Properties getClientInfo() throws SQLException; // 终止/关闭打开的连接 void abort(Executor executor) throws SQLException; void close() throws SQLException; // 连接是否已经关闭 boolean isClosed() throws SQLException; // 清除/获取数据库访问的警告信息 void clearWarnings() throws SQLException; SQLWarning getWarnings() throws SQLException; // 连接是否仍有效,即未关闭 boolean isValid(int timeout) throws SQLException; // 设置/获取驱动等待数据库请求完成的毫秒数 void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException; int getNetworkTimeout() throws SQLException; // 设置/获取连接是否用于只读 void setReadOnly(boolean readOnly) throws SQLException; boolean isReadOnly() throws SQLException; // 设置/获取当前连接到的数据库 void setSchema(String schema) throws SQLException; String getSchema() throws SQLException; // 设置/获取当前连接创建的 Statement 可以获取到的 ResultSet 的保持性 void setHoldability(int holdability) throws SQLException; int getHoldability() throws SQLException;