背景
Seata 对业务无侵入是通过数据源代理实现的,从下图中可看出,数据源代理的实现涉及到 DataSource、Connection 以及 Statement,这几个关键知识属于 JDBC 的范畴,所以本篇从 JDBC 的视角对他们进行介绍。
一、JDBC 概述
JDBC 代表 Java 数据库连接。JDBC 是一种 Java API,用于连接数据库并执行查询。它是 JavaSE(Java 标准版)的一部分。JDBC API 使用 JDBC 驱动程序连接数据库。
在 JDBC 之前,ODBC API 是连接数据库并执行查询的数据库 API。但是,ODBC API 使用用 C 语言编写的 ODBC 驱动程序(即依赖于平台且不安全)。这就是为什么 Java 定义了自己的 API (JDBC API),它使用 JDBC 驱动程序(用 Java 语言编写)。当前的 JDBC 基于 X/Open SQL 调用级别接口。java.sql包包含 JDBC API 的类和接口。下面给出了 JDBC API 的流行接口列表:
- Driver interface
- Connection interface
- Statement interface
- PreparedStatement interface
- CallableStatement interface
- ResultSet interface
- ResultSetMetaData interface
- DatabaseMetaData interface
- RowSet interface
我们可以使用 JDBC API 来使用 Java 程序处理数据库,使用 JDBC 操作数据源大致需要以下几个步骤:
- 与数据源建立连接。
- 执行 SQL 语句,检索 SQL 执行结果
- 关闭连接。
二、与数据源建立链接
Connection
是 JDBC 对数据源连接的抽象,一旦建立了连接,使用 JDBC API 的应用程序就可以对目标数据源执行查询和更新操作。
获取Connection
有两种途径
2.1 DriverManager
这是一个在 JDBC 1.0 规范中就已经存在、完全由 JDBC API 实现的驱动管理类。MYSQL5 之前需要Class.forName(“com.mysql.cj.jdbc.Driver”)
的方式主动注册驱动。MYSQL5 之后的驱动包可以省略注册驱动的步骤,会自动加载 jar 包中 META-INF/services/java.sql.Driver 文件中的 JDBC 驱动类。通过getConnection
获取数据库连接,如下:
Connection conn = DriverManager.getConnection(url,username,password);
复制代码
2.2 DataSource:
DataSource 接口是 JDBC 2.0 API 中的新增内容,它提供了连接到数据源的另一种方法。使用 DataSource 对象是连接到数据源的首选方法。需要注意 JDBC API 中只提供了 DataSource 接口,DataSource 具体的实现由 JDBC 驱动程序提供。JDBC API 中定义了两个 DataSource 接口比较重要的扩展,用于支撑企业级应用。这两个接口分别为:
- ConnectionPoolDataSource * 支持缓存和复用
Connection
对象,主流的数据库连接池也提供了DataSource
接口的具体实现,如Druid
提供了 DruidDataSource,生产中我们会使用数据库连接池所提供的池化的Connection
。连接池通过对连接的复用,而不是每次需要操作数据源时都新建一个物理连接来显著地提高程序的效率,这样能够在很大程度上提升应用性能和伸缩性 - XADataSource * 该实例返回的 Connection 对象能够支持分布式事务;如 Druid 中会提供
DruidXADataSource
。XAConnection 接口继承了 PooledConnection 接口,因此它具有所有 PooledConnection 的特性
三、执行 sql、检索结果
3.1 创建Statement
获取到 JDBC 中的Connection
对象之后,我们可以通过Connection
对象设置事务属性,并且可以通过Connection
接口中提供的方法创建Statement
、PreparedStatement
或者CallableStatement
对象。如:
java.sql.Connection#createStatement()
复制代码
PreparedStatement和CallableStatement是Statement的子接口,Statement接口中定义了执行SQL语句的方法,但这些方法不支持参数输入。
-
PreparedStatement
- 接口继承自Statement接口,增加了参数占位符功能,当执行SQL语句时,可使用“?”作为参数占位符,然后使用其提供的其他方法为占位符设置参数值。其实例对象包含已编译的 SQL 语句,由于已预编译过,所以其执行速度要快于 Statement 对象。因此,多次执行的 SQL 语句经常创建为 PreparedStatement 对象,以提高效率。(这里挖个坑,因为其参数设置机制,在实践中也可能会遇到其带来的问题)
-
CallableStatement
- 接口继承自PreparedStatement接口,在PreparedStatement的基础上增加了调用存储过程并检索调用结果的功能。
3.2 执行 sql
Statement
接口可以理解为 JDBC API 中提供的 SQL 语句的执行器,我们调用Statement
接口中定义的不同方法以实现不同的结果:
- 调用 executeQuery()方法执行查询操作
- 调用 executeUpdate()方法执行更新操作
- 调用 executeBatch()方法执行批量处理
3.3 获取结果
对结果的处理则:
- 通过 getResultSet()方法来获取查询结果集,ResultSet 对象代表查询操作的结果集
- 通过 ResultSet 对象的 getMetaData()方法获取结果集元数据信息,该方法返回一个 ResultSetMetaData 对象,我们可以通过 ResultSetMetaData 对象获取结果集中所有的字段名称、字段数量、字段数据类型等信息
- 通过 getUpdateCount()方法来获取更新操作影响的行数
3.4 Connection、Statement、ResultSet 之间的关系
四 关闭 Connection 对象
当使用完 Connection 对象后,需要显式地关闭该对象。Connection 中的 close()方法用于关闭 Connection 对象,由该 Connection 对象创建的所有 Statement 对象也都会被关闭。连接池的实现的 close()方法中会把 Connection 回收到连接池中。