MyBatis3源码深度解析(三)Connnection

news2024/9/24 11:21:44

文章目录

    • 前言
    • 2.3 Connnection
      • 2.3.1 JDBC驱动程序的类型
        • 2.3.1.1 JDBC-ODBC Bridge Driver
        • 2.3.1.2 Native API Driver
        • 2.3.1.3 HDBC-Net Driver
        • 2.3.1.4 Native Protocol Driver
      • 2.3.2 java.sql.Driver
        • 2.3.2.1 静态代码块加载驱动类
        • 2.3.2.2 SPI机制加载驱动类
      • 2.3.3 DriverManager
        • 2.3.3.1 registerDriver&deregisterDriver
        • 2.3.3.2 getConnection
      • 2.3.4 javax.sql.DataSource
        • 2.3.4.1 DataSource接口介绍
        • 2.3.4.2 使用JNDI提高可移植性
      • 2.3.6 关闭Connection

前言

本节将详细研究Connection接口的相关内容,例如JDBC驱动程序的类型、DriverManager类、Driver接口以及DataSource接口等。

2.3 Connnection

一个Connection对象表示通过JDBC驱动程序与数据源建立的连接。

这里的数据源可以是关系型数据库管理系统(DBMS)、文件系统或者其他通过JDBC驱动访问的数据源。

使用JDBC API的应用程序可能需要维护多个Connection对象,一个Connection对象可能访问多个数据源,也可能访问单个数据源。

获取JDBC中的Connection对象有两种方式:

(1)通过JDBC API中提供的DriverManager类获取;
(2)通过DataSource接口的实现类获取(推荐使用)。

目前使用比较广泛的数据库连接池,如C3P0、DBCP、Druid等都是javax.sql.DataSource接口的具体实现。

2.3.1 JDBC驱动程序的类型

2.3.1.1 JDBC-ODBC Bridge Driver

SUN公司发布JDBC规范时,市面上并没有成熟的JDBC驱动程序可用,但微软的ODBC(Open-DataBase-Connectivity,开放式数据库互连)技术已经非常成熟,通过ODBC驱动程序可以连接所有类型的数据源。

因此,SUN公司发布了JDBC-ODBC桥接驱动,在JDBC和ODBC之间建立一个桥连接,利用现成的ODBC框架将JDBC调用转换为ODBC调用

JDBC-ODBC Bridge Driver
但是,由于桥连接的限制,并非所有功能都能直接转换并正常调用,同时多层调用转换对性能也有一定的影响,在JDBC规范中一般不推荐采用该方式

2.3.1.2 Native API Driver

这类驱动程序会直接调用数据库提供的原生链接库或客户端,因为没有中间过程,访问速度通常表现良好。

Native API Driver
但由于驱动程序和链接库或客户端绑定,无法达到JDBC跨平台的基本目的,因此在JDBC规范中也不推荐该类驱动程序

2.3.1.3 HDBC-Net Driver

这类驱动程序会将JDBC调用转换为独立于数据库的协议,然后通过特定的中间件或服务器转换为数据库通信协议,主要目的是获取更好的架构灵活性。

HDBC-Net Driver
然而,通过中间服务器转换会对性能造成一定影响。这类驱动程序并不常见,微软的ADO.NET属于这种架构。

2.3.1.4 Native Protocol Driver

这是最常见的JDBC驱动程序,开发中使用的驱动包基本都属于此类,通常由数据库厂商直接提供,例如mysql-connector-java。

这类驱动程序会把JDBC调用转换为数据库特定的网络通信协议,而使用网络通信,驱动程序可以纯Java实现,支持跨平台部署,性能也较好。

Native Protocol Driver

2.3.2 java.sql.Driver

2.3.2.1 静态代码块加载驱动类

所有的JDBC驱动都必须实现Driver接口,而且实现类必须包含一个静态代码块,在该静态代码块中向DriverManager注册自己的一个实例。如MyBatis内置的HSQLDB数据库的驱动程序实现类如下:

源码1org.hsqldb.jdbc.JDBCDriver

public JDBCDriver() {
}

public static final JDBCDriver driverInstance = new JDBCDriver();
static {
    try {
        DriverManager.registerDriver(driverInstance);
    } catch (Exception e) {
    }
}

由 源码1 可知,当加载驱动类JDBCDriver时,上面的静态代码块就会执行,向DriverManager注册一个JDBCDriver驱动类的实例。

这也是为什么在使用JDBC操作数据库时需要先使用Class.forName()方法加载驱动类。而为了确保驱动类可以使用Class.forName()方法进行加载,它必须提供一个无参数的构造方法。

2.3.2.2 SPI机制加载驱动类

在JDBC 4.0版本之前,使用DriverManager获取Connection对象都需要通过代码显式地加载驱动类,如:

Class.forName("org.hsqldb.jdbcDriver");

JDBC 4.0及以上的版本,对DriverManager的getConnection方法做了增强,可以通过Java的SPI机制加载驱动。

符合JDBC 4.0及以上版本的驱动程序的jar包中必须存在一个 META-INF\services\java.sql.Driver 文件,并在该文件中指定Driver接口的实现类。

如hsqldb-2.4.0.jar中:

hsqldb-2.4.0.jar中的SPI机制
在DriverManager类中,定义了一段静态代码块:

源码2DriverManager.java

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}

private static void loadInitialDrivers() {
    String drivers;
    try {
        drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
            public String run() {
                // 读取jdbc.drivers属性指定的驱动类
                return System.getProperty("jdbc.drivers");
            }
        });
    } catch (Exception ex) {
        drivers = null;
    }

    AccessController.doPrivileged(new PrivilegedAction<Void>() {
        public Void run() {
            // 通过SPI机制加载Driver接口的实现类
            ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
            Iterator<Driver> driversIterator = loadedDrivers.iterator();
            try {
                while (driversIterator.hasNext()) {
                    driversIterator.next();
                }
            } // catch ...
            return null;
        }
    });

    println("DriverManager.initialize: jdbc.drivers = " + drivers);

    if (drivers == null || drivers.equals("")) {
        return;
    }
    String[] driversList = drivers.split(":");
    println("number of Drivers:" + driversList.length);
    for (String aDriver : driversList) {
        try {
            println("DriverManager.Initialize: loading " + aDriver);
            // 依次加载jdbc.drivers属性指定的驱动类
            Class.forName(aDriver, true,
                    ClassLoader.getSystemClassLoader());
        } catch (Exception ex) {
            println("DriverManager.Initialize: load failed: " + ex);
        }
    }
}

由 源码2 可知,静态代码块会调用loadInitialDrivers方法,该方法通过两种方式加载驱动实现类。

loadInitialDrivers方法中,首先会读取jdbc.drivers属性指定的驱动类(只是读取属性,还没加载),例如在使用命令启动项目时使用jdbc.drivers属性指定驱动类:

java -Djdbc.drivers=org.hsqldb.jdbcDriver -jar xxx.jar

随后,通过SPI机制,借助ServiceLoader读取 META-INF\services\java.sql.Driver 文件中配置的Driver接口的实现类,完成驱动实现类的加载。

最后,如果jdbc.drivers属性不为空,则使用Class.forName依次加载jdbc.drivers属性指定的驱动类。

通过一个简单示例来调试一下:

  • 自定义一个驱动实现类,并配置到VM参数中
public class MyDriver implements Driver {

    static {
        try {
            DriverManager.registerDriver(new MyDriver());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public MyDriver() {
    }
    
    // 重写Driver接口的方法 ...
    
}

配置到VM参数

  • 编写测试类
public class Example01 {

@Test
public void testSPI() {
    try {
        DriverManager.getConnection("jdbc:hsqldb:mem:mybatis", "sa", "");
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

}

以Debug方式启动项目,可以发现DriverManager中注册了2个驱动类,恰好就是自定义的MyDriver以及 META-INF\services\java.sql.Driver 文件中定义的org.hsqldb.jdbc.JDBCDriver,说明上述的两种配置方式都生效了。

DriverManager中注册了2个驱动类

2.3.3 DriverManager

由类名可知,这是一个驱动管理器,为JDBC客户端管理一组可用的驱动实现。

源码3DriverManager.java

// 注册驱动实现类
public static synchronized void registerDriver(java.sql.Driver driver)
        throws SQLException {...}
public static synchronized void registerDriver(java.sql.Driver driver,
        DriverAction da) throws SQLException {...}
        
// 解除驱动注册
public static synchronized void deregisterDriver(Driver driver)
        throws SQLException {...}

// 获取数据库连接
public static Connection getConnection(String url)
        throws SQLException {...}
public static Connection getConnection(String url,
        String user, String password) throws SQLException {...}
public static Connection getConnection(String url,
        java.util.Properties info) throws SQLException {...}
2.3.3.1 registerDriver&deregisterDriver

由 源码3 可知,registerDriver方法用于将驱动实现类注册到DriverManager中,这个方法会在驱动实现类加载时通过静态代码块隐式调用。registerDriver方法有2个重载方法:

  • registerDriver(java.sql.Driver driver):直接注册驱动实现类;
  • registerDriver(java.sql.Driver driver, DriverAction da):注册一个驱动实现类的同时,并注册一个DriverAction的实现类,以监听DriverManager使用deregisterDriver方法解除该监听器的注册时回调。
2.3.3.2 getConnection

这个方法是提供给JDBC客户端调用的,可以接收一个JDBC URL作为参数,DriverManager会对所有注册的驱动实现类进行遍历,找到能够识别该URL的驱动实现,并调用connect方法与数据库建立连接,然后返回Connection对象。

由 源码3 可知,getConnection方法有3个重载方法:

  • getConnection(String url):当连接数据库不需要用户名和密码时使用;
  • getConnection(String url, String user, String password):当连接数据库只需要用户名、密码时使用;
  • getConnection(String url, java.util.Properties info):当连接数据库除了需要用户名、密码,还需要其他额外的参数时使用。

JDBC URL的格式为:jdbc:<subprotocol>:<subname>。其中subprotocol用于指定数据库连接机制由一个或者多个驱动程序提供支持;subname的内容取决于subprotocol。

常用的数据库驱动程序的驱动实现类的类名及JDBC URL如下:

(1)Oracle

驱动程序类名:oracle.jdbc.driver.OracleDriver
JDBC URL:jdbc:oracle:thin:@//<host>:<port>/<ServiceName>jdbc:oracle:thin:@//<host>:<port>/<SID>

(2)MySql

驱动程序类名:com.mysql.jdbc.Driver
JDBC URL:jdbc:mysql://<host>:<port>/<database>

(3)IBM DB2

驱动程序类名:com.ibm.db2.jcc.DB2Driver
JDBC URL:jdbc:db2://<host>:<port>/<database>

2.3.4 javax.sql.DataSource

2.3.4.1 DataSource接口介绍

DataSource是比较推荐的获取数据源连接的一种方式。JDBC驱动程序都会实现DataSource接口,通过DataSource接口实现类的实例,返回一个Connection对象的实例。

DataSource对象表示能够提供数据源连接的数据源对象。如果数据库相关信息发生了变化,则可以简单地修改DataSource对象的属性来反映这种变化,而不用修改应用程序的任何代码。

JDBC API中有3种类型的DataSource对象,分别是DataSource、XADataSource(支持分布式事务)、ConnectionPoolDataSource(提供连接池提高系统性能和伸缩性)。

而具体有哪些DataSource对象的属性,却决于具体的实现类,不同的实现类的属性略有差异。例如在MySQL驱动的实现类MysqlDataSource对象中具有以下属性(列举部分):

  • databaseName:数据库名称;
  • user:用户名;
  • password:密码;
  • hostName:主机名;
  • port:监听端口;
  • url:JDBC URL;
  • encoding:编码。

DataSource对象的实现类会为这些属性提供对应的getter和setter方法,并在创建对象时初始化这些属性。

2.3.4.2 使用JNDI提高可移植性

使用JNDI(Java Naming and Directory Interface,Java命名和目录接口)可以提高应用程序的可移植性。在应用程序中,使用JNDI API,就可以仅通过一个逻辑名称来获取DataSource对象。当数据源的属性发生变化时,不会影响JDBC客户端的代码。

JNDI为应用程序提供了一种通过网络访问远程服务的方式,可以把一个逻辑名称和数据源建立映射关系。

下面是使用JNDI的一个示例:

public class Example02 {

    @Before
    public void before() throws IOException {
        // 创建MyBatis提供的数据源工厂
        DataSourceFactory dataSourceFactory = new PooledDataSourceFactory();
        Properties dsProps = new Properties();
        InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
        dsProps.load(inputStream);
        dataSourceFactory.setProperties(dsProps);
        // 获取数据源对象
        DataSource dataSource = dataSourceFactory.getDataSource();
        try {
            // Apache Tomcat提供的JNDI实现
            Properties jndiProps = new Properties();
            jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
            jndiProps.put(Context.URL_PKG_PREFIXES, "org.apache.naming");
            InitialContext ctx = new InitialContext(jndiProps);
            // 将逻辑名称"java:testJNDI"和数据源DataSource绑定起来
            ctx.bind("java:testJNDI", dataSource);
        } catch (NamingException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testJNDI() {
        try {
            // Apache Tomcat提供的JNDI实现
            Properties jndiProps = new Properties();
            jndiProps.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.naming.java.javaURLContextFactory");
            jndiProps.put(Context.URL_PKG_PREFIXES, "org.apache.naming");
            InitialContext ctx = new InitialContext(jndiProps);
            // 通过逻辑名称"java:testJNDI"获取数据源DataSource
            DataSource dataSource = (DataSource)ctx.lookup("java:testJNDI");
            Connection connection = dataSource.getConnection();
            Assert.assertNotNull(connection);
            System.out.println("成功获取到Connection对象...");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

在示例的代码中,创建了一个javax.naming.InitialContext实例,然后调用该实例的bind方法将字符串形式的逻辑名称 java:testJNDI 和数据源DataSource对象绑定在一起。

testJNDI方法中,调用InitialContext实例的lookup方法,传入逻辑名称 java:testJNDI,获取已经绑定的数据源DataSource对象,如果成功获取到,说明JNDI确实生效了。

需要注意的是,JDK只是提供了JNDI的规范,具体的实现由不同厂商来完成。当前示例使用的是Apache Tomcat中提供的JNDI实现,因此需要在项目中的pom.xml文件中添加相关依赖:

<dependency>
    <groupId>tomcat</groupId>
    <artifactId>naming-java</artifactId>
    <version>5.0.28</version>
</dependency>
<dependency>
    <groupId>tomcat</groupId>
    <artifactId>naming-common</artifactId>
    <version>5.0.28</version>
</dependency>
<dependency>
    <groupId>commons-logging</groupId>
    <artifactId>commons-logging</artifactId>
    <version>1.1.1</version>
</dependency>

在实际的Java EE项目中,JNDI命名服务的创建通常由应用服务器(如Tomcat、WebLogic等)来创建,开发者只需要查找命名服务并使用即可。例如,在Tomcat服务器中,通过配置其 context.xml 配置文件就可以配置JNDI数据源:

<Resource name="java:testJNDI" 
          scope="Shareable"
          type="javax.sql.DataSource"
          factory="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory"
          username="sa"
          password=""
          driverClassName="org.hsqldb.jdbcDriver"
          url="jdbc:hsqldb:mem:mybatis"/>

2.3.6 关闭Connection

源码4Connection.java

public interface Connection  extends Wrapper, AutoCloseable {
    void close() throws SQLException;
    boolean isClosed() throws SQLException;
    boolean isValid(int timeout) throws SQLException;
}

当使用完Connection对象后,需要显式地关闭该对象。由 源码4 可知,Connection接口提供了几个方法处理Connection对象地关闭。

  • close:关闭Connection对象。当调用该方法时,由该Connection对象创建的所有Statement对象都会被关闭;
  • isClosed:判断Connection对象是否已被关闭;
  • isValid:判断Connection对象是否有效。

本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1491361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

06 - ip route和route -n的区别

1 ip route和route -n的区别 ip route 和 route -n 都是用于查看和管理Linux系统路由表的命令。但下面是它们的区别&#xff1a; ip route&#xff1a;是Linux系统中的现代工具&#xff0c;它属于iproute2套件&#xff1b;它提供了更多的选项&#xff0c;可以更精确地控制路由表…

详细分析Linux内存知识并释放内存

目录 前言1. 基本知识1.1 free1.2 cat /proc/meminfo1.3 slabtop 2. 清空内存 前言 本篇文章主要分析内存 如果是磁盘空间&#xff0c;推荐阅读&#xff1a;服务器出现根目录磁盘满了解决方法 1. 基本知识 在Linux系统中&#xff0c;查看内存的基本知识包括以下几个方面&…

解决手机连接校园网同一设备老是需要重复认证的问题(+解决原理)

相信大家平时在使用校园网的时候总会遇到同一设备隔三岔五就要重复认证绑定的问题&#xff0c;这里直接附上解决方案。 打开手机的wifi-->连接校园网然后进入设置-->在隐私选项选择“使用设备MAC” 如下图&#xff0c;问题解决了&#xff01;如果想知道原理的可以继续往…

RN开发搬砖经验之-Android平台下处理后退按钮事件

基本接口 利用RN 针对Android平台提供的接口 BackHandler BackHandler需要区分类组件跟函数组件的场景&#xff0c;主要是两个组件一个基于组件生命周期的&#xff0c;一个是基于hook的&#xff0c;即注册BackHandler的事件监听与移除时机写法不同。 类组件 示例代码 impor…

24/03/05总结

easyx: #include "iostream" #include "easyx.h" #include "cstdio" using namespace std; int main() {initgraph(800, 600);setorigin(400, 300);setaspectratio(1, -1);//绘制多边形:polygon(const POINT *points,int num);//points 是一个P…

Python从0到100(二):Python语言介绍及第一个Pyhon程序

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…

输出X^N对233333取模的结果。

对任意正整数N&#xff0c;求XN%233333的值。 要求运算的时间复杂度为O(logN)。 例如X30 X15*X15X15X7*X7*XX7X3*X3*XX3X*X*X共7次乘法运算完毕。输入输出格式 输入描述: 输入两个整数X和N&#xff0c;用空格隔开&#xff0c;其中X,N<10^9。 输出描述: 输出X^N对233333取模…

【排序】详解选择排序

一、思想 选择排序的原理与思想非常直观和简单&#xff0c;它通过不断地选择未排序部分的最小&#xff08;或最大&#xff09;元素&#xff0c;并将其放到已排序部分的末尾来实现排序。 具体来说&#xff0c;选择排序的过程可以分解为以下几个步骤&#xff1a; 寻找最小&…

Android m/mm/mmm/make编译模块

一.编译成模块的前置条件 Android编译环境初始化完成后&#xff0c;我们就可以用m/mm/mmm/make命令编译源代码了。lunch命令其实是定义在build/envsetup.sh文件中的函数lunch提供的。与lunch命令一样&#xff0c;m、mm和mmm命令也分别是由定义在build/envsetup.sh文件中的函数…

防火墙:网络防御的第一道防线

目录 引言 一、安全技术与防火墙 &#xff08;一&#xff09;安全技术 &#xff08;二&#xff09;防火墙的主要功能与分类 1.防火墙的主要功能 2.防火墙的分类 二、Linux防火墙的基本认识 &#xff08;一&#xff09;Netfilter &#xff08;二&#xff09;防火墙工具…

OSPF 完全stub区域实验简述

1、OSPF 完全stub区域配置 为解决末端区域维护过大LSDB带来的问题&#xff0c;通过配置stub no-summary 完全stub,仅支持1类、2类LSA&#xff0c;ABR产生1条3类默认路由。 实验拓扑图 r1: sys sysname r1 undo info enable int loopb 0 ip add 1.1.1.1 32 quit int e0/0/0 ip …

Java ElasticSearch面试题

Java ES-ElasticSearch面试题 前言1、ElasticSearch是什么&#xff1f;2. 说说你们公司ES的集群架构&#xff0c;索引数据大小&#xff0c;分片有多少 &#xff1f;3. ES的倒排索引是什么&#xff1f;4. ES是如何实现 master 选举的?5. 描述一下 ES索引文档的过程&#xff1a;…

Spring Bean装配精解:探索自动化与显式配置之道

作为一名对技术充满热情的学习者&#xff0c;我一直以来都深刻地体会到知识的广度和深度。在这个不断演变的数字时代&#xff0c;我远非专家&#xff0c;而是一位不断追求进步的旅行者。通过这篇博客&#xff0c;我想分享我在某个领域的学习经验&#xff0c;与大家共同探讨、共…

GEE入门篇|图像分类(一):监督分类

在遥感中&#xff0c;图像分类是尝试将图像中的所有像素分类为有限数量的标记土地覆盖和/或土地利用类别。 生成的分类图像是从原始图像导出的简化专题图&#xff08;图 1&#xff09;&#xff0c; 土地覆盖和土地利用信息对于许多环境和社会经济应用至关重要&#xff0c;包括自…

boss app sig及sp参数,魔改base64(下)

本章所有样本及资料均上传123云盘,需要复刻的自行下载. boss官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘 boss app sig及sp参数,魔改base64(上)_app 魔改base64-CSDN博客 上篇boss分析sig的地址在上面了,把这个sp分析完后再把响应解密分析完就可以对boss的招聘数据…

雷卯推荐电磁兼容保护器件-MOV压敏电阻

一&#xff0e;雷卯MOV产品表格部分展示 一&#xff0e;雷卯MOV产品表格部分展示 三、MOV概述 MOV是金属氧化物压敏电阻器&#xff08;Metal Oxide Varistor&#xff09;的缩写。它是一种电子元器件&#xff0c;通常用于保护电路中的电子设备不受过电压的损害。当电路中电压超…

SwiftUI中的边框、圆角、阴影与渐变色的应用

在SwiftUI中&#xff0c;可以使用边框、圆角、阴影和渐变色来增强视图的外观和风格。 边框&#xff1a; 可以通过在视图上应用边框样式来创建边框效果。使用border()修饰符&#xff0c;并指定边框的颜色、线条宽度和圆角半径&#xff0c;例如&#xff1a; Text("Hello, …

【中国算力大会主办】2024算法、高性能计算与人工智能国际学术会议(AHPCAI 2024)

【中国算力大会主办】2024算法、高性能计算与人工智能国际学术会议&#xff08;AHPCAI 2024&#xff09; 2024 International Conference on Algorithms, High Performance Computing and Artificial Intelligence 2024算法、高性能计算与人工智能国际学术会议&#xff08;AH…

【千字总结】爬虫学习指南-2024最新版

介绍 如何自学爬虫&#xff1f;今天有一个兄弟这样问我&#xff0c;可以看到打了很多字&#xff0c;诚意肯定是很足的&#xff0c;也是对我的内容给予了肯定&#xff0c;让我非常的开心。既然难得有人问我&#xff0c;那我一定要好好做一个回答。 我下面将要说的内容没有任何话…

Unity 刚体组件的碰撞与触发器

添加刚体组件 给球体添加刚体组件&#xff0c;将脚本挂载到上面。 以下效果为&#xff1a;当球体落到平面上会消失。 using System.Collections; using System.Collections.Generic; using UnityEngine;public class c1 : MonoBehaviour {void Start(){}void Update(){}// 开…