OceanBase Connector/J 驱动程序
OceanBase Connector/J 驱动程序属于 JDBC Type 4 驱动类型,可以通过本地协议直接与数据库引擎通信。ODP/OBServer 节点支持 OceanBase Connector/J 驱动程序,同时完全兼容 MySQL 原生的 JDBC 驱动( MySQL Connector Java )。OceanBase Connector/J 驱动程序完全兼容 MySQL JDBC 的使用方式,可以自动识别 OceanBase 数据库的运行模式是 MySQL 还是 Oracle,并在协议层同时兼容这两种模式。
OceanBase Connector/J 驱动程序兼容 OB2.0 协议。
注意
OBServer 节点会依据 JDBC 驱动连接时的租户名称判断运行模式为 MySQL 或者 Oracle。Oracle 模式的租户只允许使用 Oracle 兼容的 SQL 语法。
除了支持标准的 JDBC 应用程序编程接口( API ),OceanBase Connector/J 还兼容 Oracle Driver 的使用方式,OBServer 节点的 Oracle 模式兼容 Oracle 的大部分语法。
MySQL Protocol
MySQLProtocol 实现 MySQL 驱动和 MySQLServer 之间的通信,它通过相同的序列化和反序列化规定,让数据的发送端和接收端可以准确高效地对数据进行 PRC 调用。WireShark 软件支持按照 MySQLProtocol 的格式解码,所以抓包相对来说很容易,如下图所示:
可以比较清晰的查看到驱动和 OBServer 节点( ODP )之间的交互过程和发包信息。
因为 MySQLProtocol 的存在,只要支持了它,并且表现上和 MySQLServer 一致我们就可以做到兼容 MySQL JDBC 及其他基于 MySQLProtocol 的驱动(如:MaraiDB JDBC ),所以 OBServer 节点是兼容 MySQL JDBC 的,且在 MySQL JDBC、MariaDB JDBC 的基础上开发了支持 OceanBase 的 Oracle Mode 的 OBJDBC。
文本协议和二进制协议
MySQL Protocol 有一个很重要的概念就是文本协议和二进制协议,简单的来说可以总结为:
文本协议:将 JDBC 的所有动作转换为标准的 SQL 语句,将 SQL 语句以字符串的形式发送到 OBServer 节点,OBServer 节点执行后再返回相应的结果。
二进制协议:通过协议中的 COM_STMT_PREPARE 命令来 Prepare 相应的 SQL 语句,OBServer 节点解析 SQL 语句,返回相应参数的描述信息,驱动方面填充相应的数据信息,再发送到 OBServer 节点。
文本协议的好处是不用执行一次 Prepare,而是只执行相应的 SQL;二进制协议的好处是可以 Prepare 后多次执行。
与之紧密相关的配置是 useServerPrepStmts,当它为 False 的时候采用文本协议,为 True 的时候会采用二进制协议。
如:
PreparedStatement ps = conn.prepareStatement("select * from selecttest");
ResultSet rs = ps.executeQuery();
如上的测试语句,在 useServerPrepStmts=true 和 useServerPrepStmts=false 时表现是不同的:
Request Prepare Statement
Response
Request Execute Statement
Response
通过抓包数据可以看到 useServerPrepStmts=true 的时候做的操作是 prepare + execute,而useServerPrepStmts=false 时,是执行了 SQL 语句。
Request Query {select * from selecttest}
Response
OBJDBC 版本差异
1.X 基于 MySQL JDBC 开发,开源协议为 GPL,JDK 版本要求为 JDK7~8。
2.X 基于 MaraiDB JDBC 开发,开源协议为 LGPL,JDK 要求 1.8。
JDBC 标准实现
Connection 相关
建立连接
OceanBase 数据库通过租户来区分 Oracle 模式还是 MySQL 模式,MySQL 模式的情形和 MySQL Server 表现基本一致,也可以使用 MySQL JDBC 直连 OBServer,这里我们不讨论 MySQL 模式情况,只分析 Oracle 模式的情形。
MySQL 建连接的交互过程称之为 Handshake,整个流程如下(这里描述的流程是在底层的 TCP 连接创建之后):
OBServer 端主动向 Client 端发送 Handshake Packet, 包里携带里 OBServer 端的版本、字符集和支持的能力等信息。
Client 端根据 Handshake Packet 携带的信息构造 Handshake Response Packet, 发给 OBServer 节点去做认证。
认证成功之后,OBServer 节点返回一个 OK Packet 给 Client,此时建立连接交互完成。
OK 包中携带了 2 个字节的 Status Flags , 用来表示当前连接的状态信息,一共 16 位,每一位代表一种功能。
在 Handshake Response 包中,开头的 4 个字节 capability flags 描述了 Client 所支持的能力,长度是 32 bit,每一个 bit 代表一种功能,目前 MySQL 用了前面 26 位,我们使用第 27 位来表示 Client 是否支持 Oracle 模式,具体的内容如下:
const (
clientLongPassword clientFlag = 1 << iota
clientFoundRows
clientLongFlag
clientConnectWithDB
clientNoSchema
clientCompress
clientODBC
clientLocalFiles
clientIgnoreSpace
clientProtocol41
clientInteractive
clientSSL
clientIgnoreSIGPIPE
clientTransactions
clientReserved
clientSecureConn
clientMultiStatements
clientMultiResults
clientPSMultiResults
clientPluginAuth
clientConnectAttrs
clientPluginAuthLenEncClientData
clientCanHandleExpiredPasswords
clientSessionTrack
clientDeprecateEOF
clientSupportOracleMode = 1 << 27
)
OBServer 节点接收到 Handshake Response 包之后,会解析出 username 里的租户信息,如果是 Oracle 租户,并且 capability flags 中的第 27 位是 1,会返回一个 OK 包给 Client, 否则会返回一个 Error 包给 Client, 错误信息是"Oracle tenant for current client driver is not supported"。在返回的 OK 包中,有 2 个字节的 Status Flags,代表当前连接的状态信息,一共 16 位,其中第 3 位 MySQL 没有使用,我们直接复用这一位来表示当前连接是 Oracle 模式。
初始化 Session 变量
OBJDBC 与 OBServer/obproxy 建连成功后,会进行一系列的变量初始化。初始化的变量的值是从默认的配置得到的,可以被 URL 中的配置覆盖。
origin/V4.0.0_easy_of_use
一般初始化时会执行 SQL 来做变量的初始化,示例如下:
set autocommit=1, sql_mode = concat(@@sql_mode,',STRICT_TRANS_TABLES')
set names utf8
初始化本地变量
OBJDBC 通过存储某些变量的值来和 OBServer 节点的 Session 保证一致,如:ReadOnly 和 txIsolation 等。在设置完 Session 变量后,会查询相应的变量值来初始化这些 Local Variables,并且在之后调用 API 的时候也修改相应的值以保证 OBServer 端和 JDBC 端的信息一致。
示例如下:
SELECT @@max_allowed_packet,@@system_time_zone,@@time_zone,@@auto_increment_increment,@@tx_isolation AS tx_isolation,@@session.tx_read_only AS tx_read_only from dual
select count(*) from V$TIMEZONE_NAMES // 该语句比较特殊是为了确定server是否有导入时区表
Connection 常用接口
Connection 作为一个最基础的接口主要创建其他接口或者获得 Connection 相关的信息。最常用的接口如下:
createStatement
prepareStatement
prepareCall
getMetaData
commit/rollback
Statement 功能
Statement 是功能比较单一的接口,主要就用来执行 SQL 语句,也可以用来查询数据,和使用 OBClient 等命令行工具执行 SQL 没有本质的区别。
Statement 的常用接口如下:
execute
executeQuery
setQueryTimeout
addBatch
executeBatch
setFetchSize
PrepareStatement 功能
PrepareStatement JDBC 是最常用的接口之一,他可以实现 Statement 几乎所有的功能。PrepareStatement 表示预编译的 SQL 语句的对象,SQL 语句被预编译并存储在对象中,被封装的 SQL 语句代表某一类操作,SQL 语句中允许包含动态参数"?",在执行时可以为"?"动态设置参数值。
值得注意的是,要实现真正的预编译效果需要设置url option中的useServerPreparedStmts = true,否则即使使用 PrepareStatement 也会降级成文本协议而不是二进制协议。
PrepareStatement 的常见用法如下:
ps = conn.prepareStatement("insert into pstest values(?,?)");
ps.setString(1,"string1");
ps.setInt(2,1);
ps.execute();
ps.setString(1,"string2");
ps.setInt(2,2);
ps.execute();
预编译之后的数据只需要发送 Execute 就可以,这大大提升了效率。
PrepareStatement 的常用接口如下:
addBatch
executeBatch
setFetchSize
CallableStatement 功能
CallableStatement 可以用来执行 Procedure 和 Function,并设置相应的 IN、OUT 参数,获取其返回值等。本质上 CallableStatement 是 PrepareStatement 的一个子类,也可以当做 PrepareStatement 来使用。
在执行 Connection 中的方法 PrepareCall 的时候,会解析相应的 SQL 语句,如果是调用 Procedure 或者 Function 的 SQL 语句,会使用相应的解析工具解析出 Procedure/Function 的名字,然后通过名字在 all_argument 中查询出相应的 IN、OUT 类型,通过这些信息拼装出相应的 Procedure/Function 的描述信息。
CallableStatement 中最常用的方法如下:
registerOutParameter
getXXX
ResultSet
通常通过执行查询数据库的语句生成一个表示数据库结果集的数据表。
ResultSet 对象维护一个指向其当前数据行的游标。游标最初位于第一行之前,使用 next 方法可以将光标移动到下一行,当 ResultSet 对象中没有更多行时会返回 False,故可以在 While 循环中使用它来遍历结果集。
默认的 ResultSet 对象是不可更新的,并且只有一个向前移动的游标。因此,只能从第一行到最后一行遍历一次结果集,但是可以生成可滚动或可更新的 ResultSet 对象。
ResultSet 接口提供了从当前行检索列值的Getter方法(GetBoolean、GetLong等)。可以使用列的索引号或列名来检索值。一般来说,使用列索引会更有效率。列从 1 开始编号。为了获得最大的可移植性,应按从左到右的顺序读取每行中的结果集列,并且每列应仅读取一次。
对于 Getter 方法,JDBC 驱动程序尝试将底层数据转换为 Getter 方法中指定的 JAVA 类型并返回合适的 JAVA 值。
Getter 方法用作输入的列名不区分大小写。当使用列名调用 Getter 方法并且多个列具有相同名称时,将返回第一个匹配的列值。列名选项在生成结果集的 SQL 查询中使用。对于查询中未明确命名的列,最好使用列号。如果使用列名,程序员可以通过 SQL 的 AS 子句来确保它们被唯一的列引用。
相关方法如下:
next absolute previous
getMetaData
ResultSetMetaData 功能
ResultSetMetaData 可用于获取有关 ResultSet 对象中列的类型和属性的信息,包括很多接口可以获得非常多的描述信息。
Oracle 兼容
Oracle 兼容性主要包括两方面:数据类型兼容和功能兼容,前面的标准实现也匹配了 Oracle 兼容性。比如为了兼容 Oracle 的某些数据类型 ResultSetMetaData 的方法返回值就需要做出调成。实现 CheckSum 需要在原有的MySQL Protocol 的基础上实现 CheckSum 功能,利用 HandShake 包的 Status Flag 做 Oracle MODE Flag 等。
数据类型兼容
OceanBase 兼容的 Oracle 数据类型如下:
字符类型
VARCHAR2
NVARCHAR2
CHAR
NCHAR
数字类型
NUMBER
NUMBER_FLOAT
BINARY_FLOAT
BINARY_DOUBLE
日期类型
DATE
TIMESTAMP
TIMESTAMP WITH TIME ZONE
TIMESTAMP WITH LOCAL TIME ZONE
INTERVAL
INTERVAL YEAR TO MONTH
INTERVAL DAY TO SECOND
LOB
CLOB
BLOB
复杂类型
Struct
Array
RefCursor
RAW
ROWID
功能兼容
PS Checksum
Oracle 模式下支持开启 PS 后的 CheckSum 校验,UseServerPsStmtChecksum 这个参数负责开启或者关闭这个功能。
在 Preapre 的时候通过 OBCrc32C ( CRC32 checksum 算法的纯 Java 实现,它使用 CRC32-C 多项式,iSCSI 使用的多项式与在许多支持 SSE4.2 的英特尔芯片组上实现的多项式相同 )计算一个 CheckSum,并在只收的 Execute 中都将其发送过去,如果 CheckSum 出错则 OBServer 节点会报错。




