searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

pgjdbc源码初步学习

2023-09-25 03:14:58
48
0

一、源码结构目录

      pgjdbc的源代码放在org.postgresql目录下,其结构如下所示:
      其中,每一个文件夹的功能大体如下:
    • copy:用于支持 postgresql的 COPY命令;
    • core:是程序的核心目录,包含核心的JDBC驱动程序代码,包括连接、查询处理、结果集等功能的实现;以及前后端通信的V3协议(V2版本的协议在新版本里已删除);
    • ds:用于存放数据源(DataSource)相关的代码;
    • fastpath:与快速路径(Fast Path)相关的代码。快速路径是一种优化技术,用于提高数据库查询的执行效率;
    • geometric:用于支持几何类型,如点、线、面;
    • gss、ssl、sspi:提供了安全认证的相关支持;
    • hostchooser:实现了主机选择器的逻辑,用于处理多主机的连接;当一个数据库有多个主机可用时,主机选择器负责选择一个合适的主机来建立连接。这个过程包括对各个主机的健康检查、负载均衡等功能的实现;
    • jdbc、jdbc2、jdbc3:提供了不同版本的的jdbc接口的实现代码;
    • jdbcurlresolver:提供了用于解析jdbc url的代码;
    • jre7:主要用于支持pgjdbc驱动程序在Java 7环境中的运行和使用。它包含了针对该版本的特定代码和补丁,以解决可能存在的兼容性问题或修复错误;
    • largeobject:对Postgresql的大对象数据类型的支持;
    • osgi:用于实现pgjdbc驱动程序在OSGI环境中的支持和集成;
    • plugin:实现pgjdbc驱动程序的插件机制,使得开发者可以创建自定义的插件来扩展pgjdbc的功能;
    • replication:实现pgjdbc驱动程序对复制功能的支持;
    • shaded:实现阴影(shaded)依赖机制,使得pgjdbc驱动程序可以作为一个独立的组件与其他Java应用程序进行交互和集成,而无需担心依赖冲突的问题;
    • translation:用于支持pgjdbc驱动程序的多语言翻译;
    • util:提供工具类和辅助函数,如字节流、时间处理等。
    • xa:提供支持分布式事务的数据源的代码;
    • xml:用于支持XML数据的处理和转换。

二、一般流程

      本文通过一个简单例子进行说明。在pg中创建名为customer的表,其中的表结构和示例数据如下所示:

      从上述代码可以看出,pgjdbc与pg的交互总体上可分为五步:①注册pg的驱动;②获得与pg的连接;③创建并且执行statement;④将sql发送给数据库,然后数据库将结果返回,获得最终的结果;⑤关闭连接。

三、代码分析

(一)、Class.forName(“org.postgresql.Driver“)

      该函数的作用是驱动注册。java规定,所有的驱动程序是要在静态代码块中将驱动注册至驱动管理器中。此函数的的原理是,通过反射获得org.postgresql.Driver这个类,加载此类的时候自动执行Driver类中的静态代码块,也就是register()这个函数。

      register()主要步骤如下图所示,isRegistered()判断驱动是否注册过;如果没注册的话,就会new一个Driver对象,然后在DriverManager中注册此对象。这个注册的原理是,DriverManager中有一个叫做registeredDrivers的CopyOnWriteArrayList,用来保存注册的Driver,也就是说将Driver插入这个List中就代表了注册成功。

(二)、Driver.getConnection()

      此函数的功能就是获得与pg的连接。这里面有一个容易忽略点就是,如果在idea中debug调用该函数时,它会直接跳到connect()这个函数里面运行。但实际上,Dirver.getConnection()会遍历registeredDrivers中的Driver,然后再调用connect()这个函数。一旦连接成功,直接就跳出循环。
      connect()函数的主要流程如下图所示:获得user和password;调用parseurl()去解析url,获得端口号、host等信息;然后就调用makeConnection()去连接。这里面有一个点就是,是否设置了timeout(代表loginTimeout参数)会导致不同的行为:当该参数不设置的时候,是直接调用makeConnection();如果设置了的话,则会再启动一个线程,并将其设置为守护线程,然后用该线程去连接,调用的也是makeConnection()这个函数。

1.makeConnection()

      下面介绍一下makeConnection()的功能。此函数主要是创建一个PgConnection对象,创建的步骤如下图所示,主要包括:设置fetchSize、prepareThershold参数;然后调用ConnectionFactory.openConnection(),去建立与初始化和pg的连接(创建pgStream对象);随后对binarySendOids、binaryReciveOids、typeCache、commitQuery、rollBackQuery等参数和对象进行设置和生成。

 

      其中,起主要作用的函数为ConnectionFactory.openConnection()。但是在介绍该函数之前需要简要介绍一下pg的通信协议。

2.pg通信协议

      pg 在 TCP/IP 协议之上实现了一套基于消息的通信协议。同时,为避免客户端和服务端在同一台机器时的网络通信代价,也支持在 Unix 域套接字上使用该协议。pg 至今共实现了三个版本的通信协议,现在普遍使用的是 3.0 版本。本文对3.0版本的协议进行简要介绍。
pg的通信过程可划分为startup和normal两个阶段。
  • startup 阶段,客户端尝试创建连接并发送授权信息,如果一切正常,服务端会反馈状态信息,连接成功创建,随后进入 normal 阶段。
  • normal 阶段,客户端发送请求至服务端,服务端执行命令并将结果返回给客户端。客户端请求结束后,可以主动发送消息断开连接。
      在normal阶段中,客户端可以 simpel query 和 extened query两种子协议发送请求。simple query :客户端发送字符串文本请求,后端收到后立即处理并返回结果,前后端只有一次交互。
      extended query:发送请求的过程被分为若干步骤,通常包括 Parse,Bind 和 Execute等阶段。可以使用服务端的 perpared-statement 功能,提升运行效率。pgjdbc默认采用的就是extended协议(通过preferQueryMode设置)。
      下面对parse、bind、describe、execute和sync消息的功能进行介绍:
  • parse:该消息包括参数化sql,参数占位符以及每个参数的类型;还可以指定 Statement 的名字,若不指定名字,即为一个 "unamed" 的 Statement。
  • bind:该消息携带具体的参数值、参数格式和返回列的格式。
  • describe:获取查询结果描述信息,包括列数信息、数据类型等。
  • execute:通知服务端执行请求,执行结果通过 DataRow 消息返回给客户端。
  • sync:一个请求总是以 sync 消息结束。服务端接收到 sync 消息后,关闭隐式开启的事务并回复 ReadyForQuery 消息(ReadyForQuery消息会反馈当前事务的执行状态,I表示未进行事务;T表示正在事务中;E表示事务失败)。
      然后pg的通信协议还包括一些其他的子协议,例如copy(用于支持高效导入导出)等。
 

3.ConnectionFactory.openConnection()

      下面介绍ConnectionFactory.openConnection()的功能,其作用为创建初始连接,返回一个queryExecutor。其主体流程为:
获取sslMode、GssEncMode、targetSeverType
 //获取socketFactory,用于创建socket
 SocketFactory socketFactory = SocketFactory.getSocketFactory()
while(hostChooser.host){
  ...
  // 创建pgStream对象,并设置相应参数,如newworkTimeout、keepalive;安全认证、身份认证
  PGStream newStream = tryConnect() 
  ...
  runInitialQueries() // 发送初始语句
  ...
}

      其中,runInitialQueries()发送的消息为

BEGIN
SET extra_float_digits = 3
SET application_name = 'PostgreSQL JDBC Driver'
COMMIT
      由于ConnectionFactory.openConnection()部分代码比较长,所以采用日志的方式查看关键步骤(这里并非完整日志)。
(三)、createStatement()
      在获得连接之后,就进行statement对象的创建,主要分为以下两步:
checkClosed()
new PgStatement()
      其中,checkClosed()用于检测connection连接是否断开;new PgStatement()就是新建Statement的对象,然后里面的相关参数都是从connection对象中后的,如fetchSize等。

1.statement和preparedStatement区别

  • statement每次执行sql语句时,数据库都要对该sql语句进行编译;而preparedStatement则采用预编译的方式,每次只替换定义的变量,一次编译,多次执行,效率更高;并且由于此特性,preparedStatement能够防止sql注入,而statement不行。
  • preparedStatement支持批处理。
  • preparedStatement是statement的子类。

(四)、执行sql语句

执行sql语句在本例中通过调用executeQuery()完成。在pgjdbc中,执行sql语句的方式包括executeQuery()、executeUpdate()和exexute()。
  • executeQuery()返回的是ResultSet结果集,通常是执行了select操作。大体执行流程如下:
executeWithFlags()
getSingleResultSet() //获得查询结果resultSet
  •  executeUpdate()返回的是int型数据,表示受影响的行数,通常是执行了insert、update、delete操作。大体执行流程如下:
executeWithFlags()
getUpdateCount()  // 获得受影响的行数
  •  execute()返回的是boolean型:当返回的是true时,表明结果是ResultSet;false时,则表明是受影响的行数或者没有结果。大体执行流程如下:
executeWithFlags()
      上述这三个函数在statement和preparestatement中的调用的过程虽然有所不同,但是最关键的执行部分是一样的,执行调用过程为:
 

       下面对connection.getQueryExecutor().execute()进行介绍,其主要流程如下:

...
//发送一些前导数据包,这些数据包括一些必要的元数据和协议参数
sendQueryPreamble() 
sendAutomaticSavePoint() //发送自动保存点
sendQuery() // 发送查询消息,包括parse、bind、describe、execute消息
...
sendSync() // 发送sync消息
processResult() // 接收从pg端传来的消息(包括结果),并进行相应的处理
...
      对应的日志记录如下所示:

(五)、关闭连接

1. statement.close()

      本质为关闭得到的ResultSet。

2.connection.close()

      关闭queryExecutor,但本质上是关闭pgStream。

 

0条评论
0 / 1000
z****n
4文章数
0粉丝数
z****n
4 文章 | 0 粉丝
z****n
4文章数
0粉丝数
z****n
4 文章 | 0 粉丝
原创

pgjdbc源码初步学习

2023-09-25 03:14:58
48
0

一、源码结构目录

      pgjdbc的源代码放在org.postgresql目录下,其结构如下所示:
      其中,每一个文件夹的功能大体如下:
    • copy:用于支持 postgresql的 COPY命令;
    • core:是程序的核心目录,包含核心的JDBC驱动程序代码,包括连接、查询处理、结果集等功能的实现;以及前后端通信的V3协议(V2版本的协议在新版本里已删除);
    • ds:用于存放数据源(DataSource)相关的代码;
    • fastpath:与快速路径(Fast Path)相关的代码。快速路径是一种优化技术,用于提高数据库查询的执行效率;
    • geometric:用于支持几何类型,如点、线、面;
    • gss、ssl、sspi:提供了安全认证的相关支持;
    • hostchooser:实现了主机选择器的逻辑,用于处理多主机的连接;当一个数据库有多个主机可用时,主机选择器负责选择一个合适的主机来建立连接。这个过程包括对各个主机的健康检查、负载均衡等功能的实现;
    • jdbc、jdbc2、jdbc3:提供了不同版本的的jdbc接口的实现代码;
    • jdbcurlresolver:提供了用于解析jdbc url的代码;
    • jre7:主要用于支持pgjdbc驱动程序在Java 7环境中的运行和使用。它包含了针对该版本的特定代码和补丁,以解决可能存在的兼容性问题或修复错误;
    • largeobject:对Postgresql的大对象数据类型的支持;
    • osgi:用于实现pgjdbc驱动程序在OSGI环境中的支持和集成;
    • plugin:实现pgjdbc驱动程序的插件机制,使得开发者可以创建自定义的插件来扩展pgjdbc的功能;
    • replication:实现pgjdbc驱动程序对复制功能的支持;
    • shaded:实现阴影(shaded)依赖机制,使得pgjdbc驱动程序可以作为一个独立的组件与其他Java应用程序进行交互和集成,而无需担心依赖冲突的问题;
    • translation:用于支持pgjdbc驱动程序的多语言翻译;
    • util:提供工具类和辅助函数,如字节流、时间处理等。
    • xa:提供支持分布式事务的数据源的代码;
    • xml:用于支持XML数据的处理和转换。

二、一般流程

      本文通过一个简单例子进行说明。在pg中创建名为customer的表,其中的表结构和示例数据如下所示:

      从上述代码可以看出,pgjdbc与pg的交互总体上可分为五步:①注册pg的驱动;②获得与pg的连接;③创建并且执行statement;④将sql发送给数据库,然后数据库将结果返回,获得最终的结果;⑤关闭连接。

三、代码分析

(一)、Class.forName(“org.postgresql.Driver“)

      该函数的作用是驱动注册。java规定,所有的驱动程序是要在静态代码块中将驱动注册至驱动管理器中。此函数的的原理是,通过反射获得org.postgresql.Driver这个类,加载此类的时候自动执行Driver类中的静态代码块,也就是register()这个函数。

      register()主要步骤如下图所示,isRegistered()判断驱动是否注册过;如果没注册的话,就会new一个Driver对象,然后在DriverManager中注册此对象。这个注册的原理是,DriverManager中有一个叫做registeredDrivers的CopyOnWriteArrayList,用来保存注册的Driver,也就是说将Driver插入这个List中就代表了注册成功。

(二)、Driver.getConnection()

      此函数的功能就是获得与pg的连接。这里面有一个容易忽略点就是,如果在idea中debug调用该函数时,它会直接跳到connect()这个函数里面运行。但实际上,Dirver.getConnection()会遍历registeredDrivers中的Driver,然后再调用connect()这个函数。一旦连接成功,直接就跳出循环。
      connect()函数的主要流程如下图所示:获得user和password;调用parseurl()去解析url,获得端口号、host等信息;然后就调用makeConnection()去连接。这里面有一个点就是,是否设置了timeout(代表loginTimeout参数)会导致不同的行为:当该参数不设置的时候,是直接调用makeConnection();如果设置了的话,则会再启动一个线程,并将其设置为守护线程,然后用该线程去连接,调用的也是makeConnection()这个函数。

1.makeConnection()

      下面介绍一下makeConnection()的功能。此函数主要是创建一个PgConnection对象,创建的步骤如下图所示,主要包括:设置fetchSize、prepareThershold参数;然后调用ConnectionFactory.openConnection(),去建立与初始化和pg的连接(创建pgStream对象);随后对binarySendOids、binaryReciveOids、typeCache、commitQuery、rollBackQuery等参数和对象进行设置和生成。

 

      其中,起主要作用的函数为ConnectionFactory.openConnection()。但是在介绍该函数之前需要简要介绍一下pg的通信协议。

2.pg通信协议

      pg 在 TCP/IP 协议之上实现了一套基于消息的通信协议。同时,为避免客户端和服务端在同一台机器时的网络通信代价,也支持在 Unix 域套接字上使用该协议。pg 至今共实现了三个版本的通信协议,现在普遍使用的是 3.0 版本。本文对3.0版本的协议进行简要介绍。
pg的通信过程可划分为startup和normal两个阶段。
  • startup 阶段,客户端尝试创建连接并发送授权信息,如果一切正常,服务端会反馈状态信息,连接成功创建,随后进入 normal 阶段。
  • normal 阶段,客户端发送请求至服务端,服务端执行命令并将结果返回给客户端。客户端请求结束后,可以主动发送消息断开连接。
      在normal阶段中,客户端可以 simpel query 和 extened query两种子协议发送请求。simple query :客户端发送字符串文本请求,后端收到后立即处理并返回结果,前后端只有一次交互。
      extended query:发送请求的过程被分为若干步骤,通常包括 Parse,Bind 和 Execute等阶段。可以使用服务端的 perpared-statement 功能,提升运行效率。pgjdbc默认采用的就是extended协议(通过preferQueryMode设置)。
      下面对parse、bind、describe、execute和sync消息的功能进行介绍:
  • parse:该消息包括参数化sql,参数占位符以及每个参数的类型;还可以指定 Statement 的名字,若不指定名字,即为一个 "unamed" 的 Statement。
  • bind:该消息携带具体的参数值、参数格式和返回列的格式。
  • describe:获取查询结果描述信息,包括列数信息、数据类型等。
  • execute:通知服务端执行请求,执行结果通过 DataRow 消息返回给客户端。
  • sync:一个请求总是以 sync 消息结束。服务端接收到 sync 消息后,关闭隐式开启的事务并回复 ReadyForQuery 消息(ReadyForQuery消息会反馈当前事务的执行状态,I表示未进行事务;T表示正在事务中;E表示事务失败)。
      然后pg的通信协议还包括一些其他的子协议,例如copy(用于支持高效导入导出)等。
 

3.ConnectionFactory.openConnection()

      下面介绍ConnectionFactory.openConnection()的功能,其作用为创建初始连接,返回一个queryExecutor。其主体流程为:
获取sslMode、GssEncMode、targetSeverType
 //获取socketFactory,用于创建socket
 SocketFactory socketFactory = SocketFactory.getSocketFactory()
while(hostChooser.host){
  ...
  // 创建pgStream对象,并设置相应参数,如newworkTimeout、keepalive;安全认证、身份认证
  PGStream newStream = tryConnect() 
  ...
  runInitialQueries() // 发送初始语句
  ...
}

      其中,runInitialQueries()发送的消息为

BEGIN
SET extra_float_digits = 3
SET application_name = 'PostgreSQL JDBC Driver'
COMMIT
      由于ConnectionFactory.openConnection()部分代码比较长,所以采用日志的方式查看关键步骤(这里并非完整日志)。
(三)、createStatement()
      在获得连接之后,就进行statement对象的创建,主要分为以下两步:
checkClosed()
new PgStatement()
      其中,checkClosed()用于检测connection连接是否断开;new PgStatement()就是新建Statement的对象,然后里面的相关参数都是从connection对象中后的,如fetchSize等。

1.statement和preparedStatement区别

  • statement每次执行sql语句时,数据库都要对该sql语句进行编译;而preparedStatement则采用预编译的方式,每次只替换定义的变量,一次编译,多次执行,效率更高;并且由于此特性,preparedStatement能够防止sql注入,而statement不行。
  • preparedStatement支持批处理。
  • preparedStatement是statement的子类。

(四)、执行sql语句

执行sql语句在本例中通过调用executeQuery()完成。在pgjdbc中,执行sql语句的方式包括executeQuery()、executeUpdate()和exexute()。
  • executeQuery()返回的是ResultSet结果集,通常是执行了select操作。大体执行流程如下:
executeWithFlags()
getSingleResultSet() //获得查询结果resultSet
  •  executeUpdate()返回的是int型数据,表示受影响的行数,通常是执行了insert、update、delete操作。大体执行流程如下:
executeWithFlags()
getUpdateCount()  // 获得受影响的行数
  •  execute()返回的是boolean型:当返回的是true时,表明结果是ResultSet;false时,则表明是受影响的行数或者没有结果。大体执行流程如下:
executeWithFlags()
      上述这三个函数在statement和preparestatement中的调用的过程虽然有所不同,但是最关键的执行部分是一样的,执行调用过程为:
 

       下面对connection.getQueryExecutor().execute()进行介绍,其主要流程如下:

...
//发送一些前导数据包,这些数据包括一些必要的元数据和协议参数
sendQueryPreamble() 
sendAutomaticSavePoint() //发送自动保存点
sendQuery() // 发送查询消息,包括parse、bind、describe、execute消息
...
sendSync() // 发送sync消息
processResult() // 接收从pg端传来的消息(包括结果),并进行相应的处理
...
      对应的日志记录如下所示:

(五)、关闭连接

1. statement.close()

      本质为关闭得到的ResultSet。

2.connection.close()

      关闭queryExecutor,但本质上是关闭pgStream。

 

文章来自个人专栏
postgresql
4 文章 | 1 订阅
0条评论
0 / 1000
请输入你的评论
0
0