【mybatis】不靠谱程序员之mysql环境下mybatis数据源驱动重复加载

一、程序员的可能都是不靠谱的

        请不要惹程序员生气,后果很严重!

        程序员都是不靠谱的,但是我很靠谱,请找我,不谢!


二、再NB也抵挡不住犯错

        今天再次撸一下mybatis数据源的源码,发现粗大事啦!

        Apache的程序员也有屌丝啊,想想还是很激动,原来不止我一个人是屌丝程序员啊。但也可能是我理解不够深入,所以写出来,让大家瞅瞅,群撸一下。好,回正题。

1. Mysql驱动包

        用JDBC第一步,就是导入数据库驱动jar包,安装mysql目录下就有,直接粘过来,build path一下就好了。

        写JDBC第一步,就是注册驱动。注册驱动有两种方式,一种是通过DriverManager来进行注册,需要传入驱动包中对java.sql.Driver的实现类的实例对象;另一种是利用类加载原理来做,只需要把驱动字节码加载到内存生成对应的Class对象即可(请参看另一篇博文:《类加载机制》),一般就是Class.forName或通过Class的class属性来获取。两种方式,那么到底孰优孰劣?

        因为mysql驱动包的缘故,第二种方式性能占优。mysql对java.sql.Driver的实现类中,通过静态代码块已经加载过一次数据库驱动了,因此通过第一种方式会导致驱动的重复加载,从而产生覆盖(产生覆盖,JVM判断两个对象是否相等通过equals方法,但是前提是两个对象有相同的类加载器,从而判定mysql驱动中通过静态代码块加载的驱动会被覆盖),而通过类加载的方式是不会导致重复加载驱动的,只是获得了mysql实现的驱动类的Class对象。mysql驱动包中源码如下所示,重点查看静态代码块。

package com.mysql.jdbc;

import java.sql.SQLException;

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
	// ~ Static fields/initializers
	// ---------------------------------------------

	//
	// Register ourselves with the DriverManager
	//
	static {
		try {
			java.sql.DriverManager.registerDriver(new Driver());
		} catch (SQLException E) {
			throw new RuntimeException("Can't register driver!");
		}
	}

	// ~ Constructors
	// -----------------------------------------------------------

	/**
	 * Construct a new driver and register it with DriverManager
	 * 
	 * @throws SQLException
	 *             if a database error occurs.
	 */
	public Driver() throws SQLException {
		// Required for Class.forName().newInstance()
	}
}

        实际上,根据类加载原理,如果使用的是mysql数据库,那么驱动是不需要显式加载的(如下图例证)。已经在驱动包的实现类中加载过了,直接通过DriverManager获取就可了,但是为了保证程序的通用性,所以还是显式加载一下最好,因此在写JDBC代码的时候,建议使用第二种方式来进行加载。


        那么就总结一下吧,mysql环境下使用JDBC进行数据库操作:

        1. JDBC加载数据库驱动建议使用类加载方式,这种方式不会导致驱动的重复加载,消耗性能;

        2. 如果使用DriverManager进行驱动加载,则会出现驱动重复加载,导致mysql驱动包中静态代码块加载的驱动被覆盖,空耗性能;


2. Mybatis数据源驱动

        mybatis自己做了数据源,分别是JNDI、POOLED、UNPOOLED,一般情况下,生产环境使用JNDI(请参看博文:《MyBatis Tomcat JNDI原理及源码分析》),开发环境使用POOLED,不使用数据源则使用UNPOOLED,三种方方式的细节不是本次讨论的重点因此略去。除了JNDI方式,最终获取数据库连接的都是通过org.apache.ibatis.datasource.unpooled.UnpooledDataSource类的doGetConnection方法,但是在获取连接之前都是需要加载驱动的,而mybatis则通过方法initializeDriver来进行驱动的加载,代码如下。

private synchronized void initializeDriver() throws SQLException {
    if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        Driver driverInstance = (Driver)driverType.newInstance();
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }
        问题也就出现在这里,源码不好看,请看截图。

        mybatis是通过DriverManager进行驱动加载的,也就是说,该方式是会导致驱动重复加载空耗性能的。有人会质疑注册驱动的方法传入的是一个驱动的代理对象,但实际上这个方法只是对原始驱动包装了一下,就算采用代理,静态代码块也不会消失,因此重复加载驱动空耗性能的问题依旧存在。

        补充文章写的比较早,那时候还比较嫩,分析是有纰漏的。首先,mybatis是不存在驱动重复加载的,在initializeDriver()方法的第一行,就是通过mybatis-config.xml中数据源配置属性“driver"来进行判断在ConcurrentHashMap是否已经存在相同KEY的驱动,以mysql为例,"driver"="com.mysql.jdbc.Driver",两者都是字符串,而ConcurrentHashMap是线程安全的(分段锁),因此同一数据库类型是不会导致驱动重复加载的。

        也就是说,如果mybatis同时使用了tomcat和oracle,那么只会存在两个驱动实例,一开始的if判断过不了。

        至于java.sql.DriverManager.registerDriver(Driver driver)是否会导致驱动重复注册的问题,答案是肯定的,确实是会造成驱动重复注册。DriverManager中通过CopyOnWriteArrayList线程安全的数据结构来存放驱动,只要传入的driver不为null,就会被放到ArrayList中。更严重的是,从DriverManager中获取驱动使用的时候,是通过遍历CopyOnWriteArrayList,只要拿到匹配数据库的第一个驱动就返回,以mysql为例,因此如果注册多个mysql驱动,那么每次都是第一个驱动被使用,而后面的驱动则一直闲着。不同数据库的驱动都存放在该结构中,但是无论哪一种数据库,都只有第一个驱动会被使用,其余的都会闲着浪费资源。

        java.sql.DriverManager源码分析如下。


        再回顾一下DriverManager.getConnection方法,遍历ArrayList,匹配立即返回,因此同一数据库注册多个驱动只会有第一个被使用,其余空闲,千万注意,传入的username, password, url是在连接数据库的时候才使用,而连接数据库则是通过Driver对象(实际上是driver.connec方法)。



三、真的是犯错吗

        上图中,画红线的下一句,将驱动代理和原始驱动放入到map中,也就意味着mabatis是允许多个驱动存在,实际上在mybatis-config.xml中可以配置多个environment(注意:environments标签是需要配置default属性的),不同的环境可能使用不同数据库的数据源,因此mybatis是需要存放每一个环境的数据库库驱动对象,为此,只有付出重复加载驱动空耗性能的代价换取设计上的实现,事实上,几乎只有在mysql环境下才会产生空耗问题。所以,这是一种折中选择吧,并不是真正的犯错!

         mybatis的结构是一个SqlSessionFactory中一个Configuration(mybatis的配置中心),一个Configuration一个Enviroment,每个Environment中有一个DataSource对象和一个TransactionFactory;对于数据源而言,UnpoolDataSource是直接通过DriverManager.getConnection来获取连接,而PoolDataSource的设计有一个池的概念(资源复用,就是java.sql.Connection对象复用),而获取资源一样需要通过JDBC的DriverManager.getConnection,然而这一步已经让UnpoolDataSource做了,所以包装一下,PoolDataSource只需要对UnPoolDataSOurce应用装饰模式即可。

        上面扯一堆啊,还是讲重点吧。

        实际上对于不同的enviroment,在创建SqlSessionFactory的时候是要指定的,所以不同的SqlSessionFactory中拥有各自的Configuration,因此不同的SqlSessionFactory对象中的数据源工厂也是不同的对象,根本谈不上复用二字。更通俗的说,environment中配置mysql和配置oracle可以,但是是不同的environment,所以啊对于UnpoolDataSource而言,不同实例不相往来,没什么浪费不浪费可谈。

        讲到这里,实际上还是要批评一下mybatis数据源的设计,不是说全不全面(要想全面获得db server的特性,直接轮JDBC得了),关于驱动这里啊,实际上可以设计成单例,在所有的mybatis datasource之间共享(人家也能共享,只需要Driver.connect方法),何必为了孤单单的一个Driver实例而配置一个大map,实际上终其一生啊,就放一个Driver,放个大map干嘛呀这是,您说对吧。

        至此,讨论结束。


附注:

        本文如有错漏,烦请不吝指正,谢谢!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis Plus是基于MyBatis的一个增强工具,通过提供更简洁、更方便的API,简化了对数据库的操作。相比于原生的MyBatisMyBatis Plus提供了更丰富的功能和更高效的性能。而Oracle和MySQL都是常见的关系型数据库,它们在底层实现原理和语法特性上有一些不同。 对于双数据源的配置,可以通过MyBatis Plus的多数据源配置来实现。首先,需要在配置文件中定义两个不同的数据源,分别对应Oracle和MySQL。可以使用不同的dataSource和transactionManager配置来指定每个数据源的连接和事务管理。 然后,在代码中使用@DS注解来指定具体是使用哪个数据源。@DS注解可以标记在类级别和方法级别,用来指定使用的数据源。例如,@DS("oracle")标记在类级别上,表示该类中的所有方法都使用Oracle数据源,而@DS("mysql")标记在方法级别上,表示该方法使用MySQL数据源。 通过这样的配置,就可以实现在同一个应用中同时使用Oracle和MySQL数据源。在使用的过程中,可以根据业务需求灵活选择具体使用的数据库。同时,MyBatis Plus还提供了很多便捷的方法和功能,可以更方便地进行数据库操作。 总之,通过MyBatis Plus的多数据源配置,可以很方便地实现Oracle和MySQL数据源的使用。这样的配置对于一些需要同时操作多个数据库的应用来说非常有用,可以满足不同业务需求的多样性。使用MyBatis Plus可以提高开发效率和代码质量,使数据库操作变得更简单和高效。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值