MyBatis3源码深度解析(三)Connnection

文章目录

    • 前言
    • 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源码深度解析

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用RabbitMQ实现延时消息自动取消的简单案例

一、流程图 二、导包 <!--消息队列 AMQP依赖&#xff0c;包含RabbitMQ--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId> </dependency> 三、配置文件 #消息队列 …

nodejs安装教程(及过程中的易错)

nodejs&#xff1a;Nodejs 是基于 Chrome 的 V8 引擎开发的一个 C 程序&#xff0c;目的是提供一个 JS 的运行环境。 npm&#xff1a;npm 是 Node Package Manager 的缩写&#xff0c;意思是 Node 的包管理系统&#xff0c;是最大的软件包仓库 下载nodejs 首先我们需要在node…

制作高端的电子杂志神器推荐

根据市场调查数据显示&#xff0c;越来越多的消费者开始青睐电子杂志这种阅读方式。相比传统纸质杂志&#xff0c;电子杂志具有更高的阅读体验、更便捷的分享和传播方式以及更环保的阅读方式。此外&#xff0c;越来越多的企业也开始重视电子杂志的宣传作用&#xff0c;将其作为…

一次磁盘组扩容引起的事故

一、备份磁盘头信息 1、mkdir /home/grid/bakasm 2、kfed read /dev/rhdisk /home/grid/backasm/rhdisk.txt 二、赋权 1、chmod 660 /dev/rhdisk 2、chown grid:asmadmin /dev/rhdisk 三、确认没有pvid lspv |grep hdisk 如果存在 chedev -l hdisk -a pvclear 四、调…

状态模式:有案例就这么简单

状态模式是一种行为设计模式&#xff0c;它允许对象在内部状态改变时改变它的行为。这使得对象看起来好像修改了它的类。状态模式主要由三个部分组成&#xff1a;上下文&#xff08;Context&#xff09;、状态接口&#xff08;State Interface&#xff09;和具体状态&#xff0…

使用 AJAX 提升网页数据的动态交互

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

2024年最新Android面试精讲,面试题附答案

一. 开发背景 想要成为一名优秀的Android开发&#xff0c;你需要一份完备的知识体系&#xff0c;在这里&#xff0c;让我们一起成长为自己所想的那样。 Android 相关 1. Android 之 SharedPreferences 内部原理浅析 2. Android 源码分析-消息队列和 Looper 3. Android 源码分析…

第二十一周代码(java + DFS/BFS)

2024/03/04 周一 路径之谜 题目链接 【参考代码】 dfs剪枝 #include <iostream> #include <vector> using namespace std;int n; int dx[] {-1, 0, 1, 0}, dy[] {0, 1, 0, -1}; bool visit[21][21] {false}; int north[21], west[21]; vector<int…

【Apache Camel】基础知识

【Apache Camel】基础知识 Apache Camel是什么Apache Camel基本概念和术语CamelContextEndpointsRoutesRouteBuilderComponentsMessageExchangeProcessorsDomain Specific Language&#xff08;DSL&#xff09; Apache Camel 应用执行步骤Apache Camel 示意图参考 Apache Camel…

智慧农业的新时代

1.智慧农业的发展背景及重要性 随着科技的发展&#xff0c;农业现代化已经成为我国农业发展的必然趋势。智慧农业作为农业现代化的重要组成部分&#xff0c;通过运用物联网、大数据、云计算等先进技术&#xff0c;实现对农业生产、管理和服务的智能化&#xff0c;提高农业生产效…

「媒体宣传」品牌发布会活动策划注意点

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 品牌发布会活动策划的媒体宣传方面&#xff0c;以下是关键的注意点&#xff1a; 一、目标与定位 明确核心信息和品牌重点。 设定清晰的目标受众。 二、媒体选择 针对性选择主流媒体、…

rt-thread uart驱动

uart驱动描述基于GD32F470芯片。 rt-thread提供了一套I/O设备模型&#xff0c;如果想要使用操作系统的驱动去进行操作&#xff0c;就得将具体芯片的硬件驱动注册到设备驱动框架上。 关于rt-thread的I/O设备模型相关内容可以参考 rt-thread I/O设备模型-CSDN博客文章浏览阅读55…

基于springboot的母婴商城

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本母婴商城系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&am…

js五星评价的制作方法

方法有两种&#xff0c;1、jquer插件&#xff1b;2、图片循环&#xff1b; 第一种、效果图 代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"…

Uniapp壁纸小程序源码/双端微信抖音小程序源码

Uniapp壁纸小程序源码&#xff0c;双端微信抖音小程序源码。WordPress后台的小程序。为一些做壁纸类自媒体的朋友解决变现难的问题&#xff0c;抖音壁纸常见的图片取号码&#xff0c;微信的壁纸公众号小程序均可使用该小程序进行变现&#xff0c;这款小程序后端为WordPress&…

44、网络编程/数据库相关操作练习20240306

一、代码实现数据库的创建&#xff08;员工信息表&#xff09;&#xff0c;并存储员工信息&#xff08;工号、姓名、薪资&#xff09;&#xff0c;能实现增加人员信息、删除人员信息、修改人员薪资操作。 代码&#xff1a; #include<myhead.h>int do_update(sqlite3 *p…

深色系可视化界面看腻了,来点浅色系?安排,20页来了。

只要不放在大屏上展示&#xff0c;贝格前端工场还是非常推崇浅色系的可视化界面&#xff0c;把它作为配色的首选 。浅色系可视化界面具有以下几个优势&#xff1a; 清晰明了 浅色系界面通常使用明亮的颜色&#xff0c;如白色、浅灰色等&#xff0c;使界面元素更加清晰可见。这…

微前端之什么是微前端

什么是微前端 微前端分类 基于路由的微前端&#xff1a;组件化微前端&#xff1a;iframe嵌入式微前端&#xff1a; 优点缺点 动态加载/懒加载微前端&#xff1a;微应用容器化方案&#xff1a; 微前端解决方案 single-spa阿里巴巴 Cloud Alfaiframe 方案Web ComponentsModule Fe…

【工作记录】Threejs学习笔记-引入OrbitControls

前言 前一篇文章我们介绍了three.js中的基础概念&#xff0c;并给出了展示整体流程的一个简单示例&#xff0c; 本文我们继续研究。 问题 我们在很多3d效果图上都能看到鼠标移动或者缩进实现旋转或者放大缩小的效果&#xff0c;这个在three.js中是通过OrbitControls这个组件…

计算机系统缺少cv100.dll文件解决方法(最新)

cv100.dll 是一个Windows操作系统中的动态链接库&#xff08;DLL&#xff09;文件。DLL文件是包含可由多个程序共享的代码和数据的模块&#xff0c;以减少磁盘空间占用并提高系统性能。根据收集到的信息&#xff0c;cv100.dll 文件可能与图像处理、计算机视觉相关的功能有关。 …