IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 大数据 -> Mysql的SPI实现、版本驱动对比和源码分析 -> 正文阅读

[大数据]Mysql的SPI实现、版本驱动对比和源码分析

一、SPI介绍

SPI的全名为Service Provider Interface,即服务提供程序接口,是Java提供的一套用来被第三方实现或者扩展的API,是JDK内置的一种服务提供发现机制,可以用来启用框架扩展和替换组件。优点是:①实现三方服务可插拔,②便于提供方实现、变更、扩展服务 ③简化用户配置,添加提供方依赖即可使用默认配置服务,提高开发效率。
Java的SPI约定服务的提供方需要在资源路径下的META-INF/services文件夹下面以服务的接口为文件名提供服务接口实现类,自动加载文件里所定义的类

二、Mysql的数据库驱动服务实现

1. 驱动版本:5.1.29及以下的版本

在这里插入图片描述
在这里插入图片描述

2.驱动版本:5.1.30到6.0.2之间的版本

在这里插入图片描述
在这里插入图片描述

3.驱动版本6.0.2及以上的版本

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、数据库接口实现源码

1.Java提供了数据库驱动服务接口:java.sql.Driver
2.Mysql实现的注册驱动,根据上面的版本查看,不同版本之间实现不同:

6.0.2版本之前核心注册驱动为:com.mysql.jdbc.Driver
6.0.2版本及之后注册驱动统一为:com.mysql.cj.jdbc.Driver

3.驱动管理和注册驱动

Java的数据库管理:java.sql.DriverManager
在这里插入图片描述
驱动管理器DriverManager在初始化时加载了SPI驱动服务:

// java.sql.DriverManager#loadInitialDrivers
    /**
     * Load the initial JDBC drivers by checking the System property
     * jdbc.properties and then use the {@code ServiceLoader} mechanism
     */
    static {
        loadInitialDrivers();
        println("JDBC DriverManager initialized");
    }

加载数据库驱动:

private static void loadInitialDrivers() {
        String drivers;
        try {
        // 加载配置文件中的jdbc.drivers指定的驱动
            drivers = AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    return System.getProperty("jdbc.drivers");
                }
            });
        } catch (Exception ex) {
            drivers = null;
        }
        // If the driver is packaged as a Service Provider, load it.
        // Get all the drivers through the classloader
        // exposed as a java.sql.Driver.class service.
        // ServiceLoader.load() replaces the sun.misc.Providers()
		// 加载SPI约定的/META-INF/services下面的驱动
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
            	// java.sql.Driver
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                Iterator<Driver> driversIterator = loadedDrivers.iterator();
                try{
                	// 迭代器中循环加载驱动
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                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);
                Class.forName(aDriver, true,
                        ClassLoader.getSystemClassLoader());
            } catch (Exception ex) {
                println("DriverManager.Initialize: load failed: " + ex);
            }
        }
    }

ServiceLoader中java.util.ServiceLoader#reload创建迭代器:java.util.ServiceLoader.LazyIterator

迭代器查找资源路径下面的SPI文件:

// java.util.ServiceLoader.LazyIterator#hasNextService
private boolean hasNextService() {
            if (nextName != null) {
                return true;
            }
            if (configs == null) {
                try {
                	// 查找META-INF/services/下面的指定service(java.sql.Driver)的文件
                	// fullName = META-INF/services/java.sql.Driver
                    String fullName = PREFIX + service.getName();
                    if (loader == null)
                        configs = ClassLoader.getSystemResources(fullName);
                    else
                        configs = loader.getResources(fullName);
                } catch (IOException x) {
                    fail(service, "Error locating configuration files", x);
                }
            }
            while ((pending == null) || !pending.hasNext()) {
                if (!configs.hasMoreElements()) {
                    return false;
                }
                pending = parse(service, configs.nextElement());
            }
            nextName = pending.next();
            return true;
        }
// java.util.ServiceLoader.LazyIterator#nextService
private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
           		// 加载数据库驱动
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn  + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error(); 
        }

DriverManager使用了CopyOnWriteArrayList集合存储了驱动实例集合:

 // List of registered JDBC drivers
 // 创建一个空集合
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList:

// java.util.concurrent.CopyOnWriteArrayList
// 基于数组的、线程安全的、适用于读多写少的、可变数组
// 使用Arrays.copyOf操作数组,效率低
 /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

Mysql在初始化时会注册自己的驱动到驱动管理器的集合中:
在这里插入图片描述
java.sql.DriverManager#registerDriver(java.sql.Driver, java.sql.DriverAction):
在这里插入图片描述
驱动管理DriverManager提供数据库连接的创建:
public java.sql.DriverManager#getConnection(String url)
public java.sql.DriverManager#getConnection(String url,String user, String password)
public java.sql.DriverManager#getConnection(String url,java.util.Properties info)
private java.sql.DriverManager#getConnection(String url, java.util.Properties info, Class<?> caller)

// java.sql.DriverManager#getConnection(java.lang.String, java.util.Properties, java.lang.Class<?>)
// 核心代码片段,从registeredDrivers驱动集合中获取数据库驱动连接
for(DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then skip it.
            if(isDriverAllowed(aDriver.driver, callerCL)) {
                try {
                    println("    trying " + aDriver.driver.getClass().getName());
                    Connection con = aDriver.driver.connect(url, info);
                    if (con != null) {
                        // Success!
                        println("getConnection returning " + aDriver.driver.getClass().getName());
                        return (con);
                    }
                } catch (SQLException ex) {
                    if (reason == null) {
                        reason = ex;
                    }
                }

            } else {
                println("    skipping: " + aDriver.getClass().getName());
            }

        }
4.旧版驱动创建数据库连接-NonRegisteringDriver
// com.mysql.jdbc.NonRegisteringDriver
//	private static final String REPLICATION_URL_PREFIX = "jdbc:mysql:replication://";

//	private static final String URL_PREFIX = "jdbc:mysql://";

//	private static final String MXJ_URL_PREFIX = "jdbc:mysql:mxj://";

//	public static final String LOADBALANCE_URL_PREFIX = "jdbc:mysql:loadbalance://";
public java.sql.Connection connect(String url, Properties info)
			throws SQLException {
		if (url != null) {
			if (StringUtils.startsWithIgnoreCase(url, LOADBALANCE_URL_PREFIX)) {
				return connectLoadBalanced(url, info);
			} else if (StringUtils.startsWithIgnoreCase(url,
					REPLICATION_URL_PREFIX)) {
				return connectReplicationConnection(url, info);
			}
		}

		Properties props = null;

		if ((props = parseURL(url, info)) == null) {
			return null;
		}

		if (!"1".equals(props.getProperty(NUM_HOSTS_PROPERTY_KEY))) {
			return connectFailover(url, info);
		}
		
		try {
			Connection newConn = com.mysql.jdbc.ConnectionImpl.getInstance(
					host(props), port(props), props, database(props), url);
			
			return newConn;
		} catch (SQLException sqlEx) {
			// Don't wrap SQLExceptions, throw
			// them un-changed.
			throw sqlEx;
		} catch (Exception ex) {
			SQLException sqlEx = SQLError.createSQLException(Messages
					.getString("NonRegisteringDriver.17") //$NON-NLS-1$
					+ ex.toString()
					+ Messages.getString("NonRegisteringDriver.18"), //$NON-NLS-1$
					SQLError.SQL_STATE_UNABLE_TO_CONNECT_TO_DATASOURCE, null);
			
			sqlEx.initCause(ex);
			
			throw sqlEx;
		}
	}
5.新版驱动基于不同的连接类型创建数据库连接-NonRegisteringDriver

相对于旧版,做了很多优化:

// com.mysql.cj.jdbc.NonRegisteringDriver
public Connection connect(String url, Properties info) throws SQLException {
        try {
            try {
            	// 验证是否时MySQL的连接地址
                if (!ConnectionUrl.acceptsUrl(url)) {
                    return null;
                } else {
                	// 获取解析后的连接信息
                    ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
                    // 基于连接类型创建不同的数据库连接
                    switch(conStr.getType()) {
                    case SINGLE_CONNECTION:
                        return ConnectionImpl.getInstance(conStr.getMainHost());
                    case FAILOVER_CONNECTION:
                    case FAILOVER_DNS_SRV_CONNECTION:
                        return FailoverConnectionProxy.createProxyInstance(conStr);
                    case LOADBALANCE_CONNECTION:
                    case LOADBALANCE_DNS_SRV_CONNECTION:
                        return LoadBalancedConnectionProxy.createProxyInstance(conStr);
                    case REPLICATION_CONNECTION:
                    case REPLICATION_DNS_SRV_CONNECTION:
                        return ReplicationConnectionProxy.createProxyInstance(conStr);
                    default:
                        return null;
                    }
                }
            } catch (UnsupportedConnectionStringException var5) {
                return null;
            } catch (CJException var6) {
                throw (UnableToConnectException)ExceptionFactory.createException(UnableToConnectException.class, Messages.getString("NonRegisteringDriver.17", new Object[]{var6.toString()}), var6);
            }
        } catch (CJException var7) {
            throw SQLExceptionsMapping.translateException(var7);
        }
    }

在这里插入图片描述

6.双重校验和可重入读写锁的使用实例

com.mysql.cj.conf.ConnectionUrl#getConnectionUrlInstance

// 可重入读写锁
// 在线程持有读锁的情况下,该线程不能取得写锁
// 在线程持有写锁的情况下,该线程可以继续获取读锁
// 一个线程要想同时持有写锁和读锁,必须先获取写锁再获取读锁;写锁可以“降级”为读锁;读锁不能“升级”为写锁。
//  private static final ReadWriteLock rwLock = new ReentrantReadWriteLock();
public static ConnectionUrl getConnectionUrlInstance(String connString, Properties info) {
        if (connString == null) {
            throw ExceptionFactory.createException(WrongArgumentException.class, Messages.getString("ConnectionString.0"));
        }
        String connStringCacheKey = buildConnectionStringCacheKey(connString, info);
        ConnectionUrl connectionUrl;
	    // 所有并发线程获取读锁,能成功获取读锁,说明没有线程持有写锁
        rwLock.readLock().lock();
        connectionUrl = connectionUrlCache.get(connStringCacheKey);
        // 第一次校验
        if (connectionUrl == null) {
       		// 所有并发线程释放自己的读锁
            rwLock.readLock().unlock();
            // 所有并发线程抢占写锁,获取写锁之后,拒绝其他线程的读写操作
            rwLock.writeLock().lock();
            try {
            	// 第二次校验
                // Check again, in the meantime it could have been cached by another thread.
                connectionUrl = connectionUrlCache.get(connStringCacheKey);
                if (connectionUrl == null) {
                    ConnectionUrlParser connStrParser = ConnectionUrlParser.parseConnectionString(connString);
                    connectionUrl = Type.getConnectionUrlInstance(connStrParser, info);
                    connectionUrlCache.put(connStringCacheKey, connectionUrl);
                }
                // 当前线程已经持有写锁,可以继续获取读锁
                rwLock.readLock().lock();
            } finally {
            	// 释放自己的写锁,完成锁降级为读锁
                rwLock.writeLock().unlock();
            }
        }
        // 释放自己的读锁
        rwLock.readLock().unlock();
        return connectionUrl;
    }
  大数据 最新文章
实现Kafka至少消费一次
亚马逊云科技:还在苦于ETL?Zero ETL的时代
初探MapReduce
【SpringBoot框架篇】32.基于注解+redis实现
Elasticsearch:如何减少 Elasticsearch 集
Go redis操作
Redis面试题
专题五 Redis高并发场景
基于GBase8s和Calcite的多数据源查询
Redis——底层数据结构原理
上一篇文章      下一篇文章      查看所有文章
加:2022-07-04 22:59:50  更:2022-07-04 23:02:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 0:18:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码