目录
一、 背景介绍
二、 使用介绍
(1)引入依赖模块
(2)建立测试类
三、 JDBC模块设计方案
(1)驱动类UJESSQLDriver
(2)JDBC连接器UJESSQLConnection
( 3)执行对象UJESSQLStatement/UJESSQLPreStatement
(4)结果集UJESSQLResultSet
(5)错误码方案
四、 实现方案总结
五、 参考文献
相关文章分享: Linkis JDBC是如何适配Tableau的?

01
—
背景介绍
Linkis作为大数据平台中间件,连接了底层的计算存储和上层的开发应用,统一了任务的调度和执行,在JDBC模块开发出来之前,向Linkis提交SQL执行任务到Spark、Hive执行支持websocket和restful的方式。为了多样化与Linkis的交互方式,便捷用户开发流程,轻量化客户端的任务提交过程,JDBC的支持无疑是非常值得考虑的。
JDBC(Java Data Base Connectivity, java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。JDBC提供的主要功能是:1、同一个数据库建立连接;2、向数据库发送SQL语句;3、处理数据库返回的结果。
在Linkis中,JDBC模块属于Linkis-UJES下面的一个子模块,UJES即Unified Job Execution Service(统一作业执行服务),UJES是Linkis最初版本的雏形,提供了基础的任务提交和结果集查询的对外服务,查询时JDBC向Linkis-UJES客户端提交SQL执行,返回得到结果集,用户在只需要像使用mysql的JDBC那样操作,大大降低了学习成本,而实现这些功能仅仅需要用户引入一个Linkis JDBC的JAR包即可。
需要区分的是,Linkis还提供JDBC引擎,例如在DataSphereStudio中可以创建JDBC脚本通过JDBC引擎提交任务到Linkis,但该引擎仅模拟实现了JDBC的部分方法,并没有规范性的实现sun提供的JDBC 4.0的完整接口方案,无法向外提供规范的SDK服务。而本文所指的Linkis JDBC模块是实现了一套接口的完整方案。

02
—
使用介绍
(1)引入依赖模块
第一种方式在pom里面依赖JDBC模块:
1 `<dependency>` 2 3 4 ` <groupId>com.webank.wedatasphere.linkis</groupId>` 5 6 7 ` <artifactId>linkis-ujes-jdbc</artifactId>` 8 9 10 ` <version>0.9.1</version>` 11 12 13 ` </dependency>` 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
注意如果引入不到该jar包,需要在ujes/jdbc目录里面执行mvn install -Dmaven.test.skip=true进行本地安装
第二种方式通过打包和编译:
**Step1:**在Linkis项目中进入到ujes/jdbc目录然后在终端输入指令进行打包mvn assembly:assembly -Dmaven.test.skip=true 该打包指令会跳过单元测试的运行和测试代码的编译,并将JDBC模块需要的依赖一并打包进Jar包之中。
**Step2:**打包完成后在JDBC的target目录下会生成两个Jar包,Jar包名称中包含dependencies字样的那个就是我们需要的Jar包
(2)建立测试类
建立Java的测试类UJESClientImplTestJ,具体接口含义可以见注释:
1public static void main(String[] args) throws SQLException, ClassNotFoundException { 2 3 4 5 6 7 8 9 10 11 12 13 14

1 图 2 3 4 5 2-1 Linkis JDBC 6 7 8 9 任务执行结果 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
03
—
模块设计方案
Linkis JDBC模块设计的初衷是为了方便用户通过JDBC的方式,便捷的提交SQL任务到Linkis中执行,是客户端轻量化追求的一种体现,该模块的类大多以UJESSQL开头,表示JDBC模块属于linkis的ujes(Unified Job Execution Service,统一任务执行服务)模块的一部分。
Linkis的JDBC模块包含了五个关键的实现类:
-
UJESSQLDriver
-
UJESSQLConnection
-
UJESSQLStatement
-
UJESSQLPreStatement
-
UJESSQLResultSet
以及许多额外的辅助类,例如数据库元数据UJESSQLDatabaseMetaData,任务执行返回的结果集元数据UJESSQLResultMetaData等。
当UJESSQLDriver通过反射机制注册到DriverManager后,通过DM可以拿到UJESSQLConnection,接着便可以正常进行SQL任务提交和获取结果集,下面是结果集获取时的方法调用时序图:

下面将逐一介绍JDBC关键类在Linkis中的实现方案。
(1)驱动类UJESSQLDriver
在 JDBC的层次上,sun主要定义了一个接口Driver和两个类:DirverManager和DriverInfo,每个JDBC驱动程序必须实现 Driver接口,例如MySql的Connector/J驱动中,叫做com.mysql.jdbc.Driver,在Linkis的JDBC中的驱动实现类为UJESSQLDriver。使用时通过Class.forName("com.webank.wedatasphere.linkis.ujes.jdbc.UJESSQLDriver")的方法显示地让JVM尝试加载类,并相应的调用静态代码块完成驱动类的注册。UJESSQLDriver的主要代码如下:
static {
通过调用DriverManager的注册方法registerDriver将该驱动类注册到DriverManager中,当用户调用DriverManager的getConnection时,DriverManager会检索所有已经注册的驱动类,并根据驱动类的类名和请求URL中的类名进行对比,寻找出对应的驱动类。
(2)JDBC连接器UJESSQLConnection
Linkis JDBC中连接器为UJESSQLConnection,实现了java.sql.connection接口。注册驱动之后,可以通过传入指定的数据库连接路径,用户名和密码便可获取数据库连接对象。
conn = (UJESSQLConnection) DriverManager
DriverManager 的getConnection方法将传入的参数进行处理和转换,调用Driver的connect方法,再将参数传入UJESSQLConnection的构造器中初始化,返回给用户。下面是UJESSQLDriver实现的的connect方法:
override def connect(url: String, info: Properties): Connection = if(acceptsURL(url)) {
在构造好连接参数之后,会调用UJESClientFactory.getUJESClient(prop)方法创建一个新的ujesclient的linkis访问客户端,用于提交和查询linkis任务。
def getUJESClient(props: Properties):UJESClient = {
将创建好的ujesclient和数据库参数props传入UJESSQLConnection的构造器,最终得到一个完整的UJESSQLConnection对象。
(3)执行对象UJESSQLStatement/UJESSQLPreStatement
执行对象在整个JDBC连接和使用的生命周期中,属于请求保存者和执行者的身份,每个JDBC连接器在生成之后,可以通过调用连接器Connection的createStatement方法获取执行对象,类似地,也可以通过Connection的prepareStatement方法获取预执行对象。
//获取执行对象
UJESSQLStatement中最为重要的方法execute作为提交SQL任务执行的入口,任务提交的执行主流程如下:
Step1 :调用hook修改sql。
Step2 :生成用于提交linkis任务的action。
Step3 :利用ujes客户端提交job到linkis执行。
Step4 :检测job的状态翻转。
Step5 :获取结果集ResultSet。
UJESSQLStatement中提交任务执行的Execute方法的代码:
override defexecute(sql: String): Boolean = throwWhenClosed {
同样的UJESPrepareStatement中的excute方法继承自Statement,原理一致。
UJESPrepareStatement与UJESStatement的不同之处在于,statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement是预编译的, 且支持批处理。
(4)结果集UJESSQLResultSet
当用户提交完SQL任务到Linkis后,会检测用户的job是否已经完成,完成时调用getResultSet的方法获取结果集UJESSQLResultSet。
在UJESSQLResultSet初次加载的时候,java虚拟机会调用初始化的init()方法,该方法会执行三个初始化的步骤,用于构建结果集:
**Step1:**通过resultSetResultInit方法设置获取结果集相关的参数,如当前用户和结果集路径,然后通过ujesClient拿到结果集。
**Step2:**通过metaDataInit方法获取结果集的元数据。
**Step3:**通过resultSetInit方法获取结果集的内容。
private def init(): Unit = {
UJESSQLResultSet中的next()方法将currentRowCursor作为移动游标。每次从结果集中读取数据后都会相应地更新游标的位置。如果next方法返回true,则可以调用getXXX()方法获取相关字段数据,反之则说明当前游标并未指向一条有效记录,读取过程直接结束。
override def next(): Boolean = {
当next() 方法返回true时,会相应地调用getXXX()方法读取数据。以getString()方法为例:
override def getString(columnIndex: Int): String = {
该方法调用getColumnValue读取数据,并将其转化为String类型的值返回。
(5)错误码方案
为了便于用户利用Linkis JDBC提交SQL执行,减少错误排查时间,我们在Linkis JDBC中对常见的错误生成了错误码,尽量覆盖整个JDBC提交和执行过程。错误码编号范围初步设定在80000~80100,常见的错误类型包括:参数类型错误、方法暂不支持、操作逻辑出错以及返回类型错误等。
public enum UJESSQLErrorCode {

04
—
实现方案总结
Linkis JDBC模块设计的初衷是为了用户能够方便的通过JDBC的方式提交SQL任务到LInkis执行,在实现的过程中,我们参考了文章[1]进行初步的框架设计,实现过程中对于任务的提交和封装参考了Linkis ujes中与job相关的文档,阅读了一些JDBC相关的文章[2][3]。设计的过程中仍有一部分非必要的接口没有实现,这是参考Kylin、Hive等项目中JDBC模块设计综合考量后的结果,在不影响使用效果的前提下降低开发成本。
Linkis本身作为大数据产品的连接器,具有强大的集成和可拓展性,JDBC模块也是Linkis的向外兼容的一个具体实现,期待服务于社区一岁多的Linkis能够茁壮成长,在大家的共同栽培下枝繁叶茂。

05
—
参考文献
[1] create-your-own-type-3-jdbc-driver
https://www.javaworld.com/article/2074249/create-your-own-type-3-jdbc-driver--part-1.html
[2] Java JDBC的优雅设计
https://blog.csdn.net/yisizhu/article/details/104025220
[3] Class.forName加载JDBC驱动程序时,底层都做了些什么???
https://www.cnblogs.com/liuxianan/archive/2012/08/04/2623258.html
WeDataSphere,BIG DATA MADE EASY.
用心做一个有温度的开源社区
欢迎关注

扫码关注我们
微信号公众号 : WeDataSphere
GitHub:WeDataSphere
如果喜欢我们的产品或文章,请给我们的GitHub点上你宝贵的star和fork哦~~
本文分享自微信公众号 - WeDataSphere(gh_273e85fce73b)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。