适配不同版本且不兼容的数据库驱动杂记

场景
在项目中适配不同版本的数据库驱动是常有的事情,比如mysql5.7和mysql8.0驱动的适配,只需要在pom.xml文件中引入两个版本驱动的依赖,然后获取jdbc驱动时区分**类的完全限定名称**​`com.mysql.jdbc.Driver`和`com.mysql.cj.jdbc.Driver`即可。但是对于**类的完全限定名称**相同,并且高版本无法兼容低版本驱动时,以**类的完全限定名称**去获取指定驱动的方法行不通了。比如达梦6的驱动和达梦7、8驱动的适配,6与7、8版本相互不兼容,且两者驱动类路径`dm.jdbc.driver.DmDriver`相同无法使用`Class.forName("")`​**去获取驱动。**
驱动类的加载过程

我们所使用的加载过程是由Jvm的类加载机制实现的,就以Jdbc连接数据库为例

public class JdbcTest {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");
        Connection conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "123456");
        ...
    }
}

Class.forName("com.mysql.jdbc.Driver")方法实现了对驱动类的加载,在jdbc4.0没有采用SPI机制之前,因为我们所引用的驱动jar包并属于Bootstrap ClassLoader和Extension ClassLoader默认加载jar包,需要手动调用Class.forName("com.mysql.jdbc.Driver")去调用Application ClassLoader加载器加载Driver驱动类。

    @CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
		// ClassLoader.getClassLoader(caller), caller) 获取了 caller 类的类加载器 ClassLoader
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

当采用Class.forName(className)加载驱动类时,会利用反射机制将Driver类加载到jvm中,同时对类进行解释,并执行类中的static代码块.比如mysql的驱动类,静态代码块中会将自身驱动注册到DriverManager中

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            // 将驱动注册到DriverManager驱动集合registeredDrivers中
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}
public class DriverManager {


    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

最终当我们调用DriverManager.getConnection方法就会从registeredDrivers集合中取出所需的驱动

private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
        /*
         * When callerCl is null, we should check the application's
         * (which is invoking this class indirectly)
         * classloader, so that the JDBC driver class outside rt.jar
         * can be loaded from here.
         */
        ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
        synchronized(DriverManager.class) {
            // synchronize loading of the correct classloader.
            if (callerCL == null) {
                callerCL = Thread.currentThread().getContextClassLoader();
            }
        }

        if(url == null) {
            throw new SQLException("The url cannot be null", "08001");
        }

        println("DriverManager.getConnection(\"" + url + "\")");

        // Walk through the loaded registeredDrivers attempting to make a connection.
        // Remember the first exception that gets raised so we can reraise it.
        SQLException reason = null;

        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());
            }

        }

        // if we got here nobody could connect.
        if (reason != null)    {
            println("getConnection failed: " + reason);
            throw reason;
        }

        println("getConnection: no suitable driver found for "+ url);
        throw new SQLException("No suitable driver found for "+ url, "08001");
    }

获取类加载器后,会调用loadClass方法对我们className进行类加载,加载的逻辑为双亲委派逻辑。BootStrapClassLoader ->ExtClassLoader -> AppClassLoader ​ 当AppClassLoader加载不到类时就会报ClassNotFoundException的异常。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

适配思路

对于不同版本,驱动类路径相同,且高低版本无法兼容的驱动包如何在项目中适配的问题主要有一下两个思路

  1. 修改其中一个jar包中驱动类的结构,使两个jar包的驱动类路径区分开来。
  2. 加载驱动时使用自定义加载驱动方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值