spring 项目 jdbc 兼容达梦6,7,8不同版本的驱动jar包

背景

当项目工程中需要同时引入不同版本的驱动包时,如果老版本和新版本的驱动jar驱动类的完全限定名称一样,同时新版本的jar包没有向下兼容老版本的驱动,就会出现创建连接无法确定驱动包的情况。比如达梦6的驱动包和达梦7、8的驱动包,达梦7、8的驱动包并不兼容达梦6,同时它们的完全限定名称都是一样的,当连接达梦6、7、8时驱动包的加载就是混乱的。

老的创建连接方式

Class.forName("dm.jdbc.dmDriver");
connection = DriverManager.getConnection("jdbc:dm:http://127.0.0.1:5236/SYSDBA", "用户名", "密码");
使用DriverManager获取连接的源码分析过程

1、Class.forName()方法的加载过程,在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();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

2、当采用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!");
        }
    }
}

因此当采用Class.forName(className)加载驱动类时,我们的驱动就会加载到DriverManager中的registeredDrivers已注册JDBC驱动程序列表中。

3、通过DriverManager.getConnection()方法获取连接时,无法指定类加载器/驱动对象。我们通过DriverManager.getConnection()的源码也可以看到getConnection方法中寻找驱动过程是对registeredDrivers已注册JDBC驱动程序列表进行for循环遍历,进而判断每个驱动是有加载驱动程序的权限,有就根据对应驱动获取连接。

    // 已注册JDBC驱动程序列表
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    ...
    private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
        ...
        // 遍历 registeredDrivers 已注册JDBC驱动程序列表
        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、当从驱动集合中取出驱动时,是根据isDriverAllowed方法判断是否有加载驱动程序的权限的,实际上isDriverAllowed方法加载驱动程序的权限是据驱动的完全限定名称重新加载一遍驱动类,判断再次加载的驱动类是否和从驱动集合中取出的驱动类相同,相同则有加载权限,反正则没有。

    // 判断每个驱动是有加载驱动程序的权限
    private static boolean isDriverAllowed(Driver driver, ClassLoader classLoader) {
        boolean result = false;
        if(driver != null) {
            Class<?> aClass = null;
            try {
                // 实际上会根据驱动的完全限定名称重新加载一遍驱动类
                aClass =  Class.forName(driver.getClass().getName(), true, classLoader);
            } catch (Exception ex) {
                result = false;
            }
            // 判断再次加载的驱动类是否和从驱动集合中取出的驱动类相同,相同则有加载权限,反正则没有
            result = ( aClass == driver.getClass() ) ? true : false;
        }

        return result;
    }

由此可见整个创建连接的过程就是:

  • 使用DriverManager创建连接时,Class.forName(className)方法会加载WEB-INF/lib目录下的jar到DriverManager已注册JDBC驱动程序列表
  • 然后再调用DriverManager.getConnection()方法,从已注册JDBC驱动程序列表中找到具有加载驱动程序的权限第一个驱动。
  • 根据这个驱动获取连接
兼容思路
根据DriverManager创建连接的过程我们发现,只要将Dm6的jar驱动包放在`WEB-INF/lib`目录下,`Class.forName(className)`方法就会加载到Dm6驱动。但是在for循环遍历驱动时如果注册Dm7的驱动要在Dm6前面,我们获取的连接就是Dm7的,而不是Dm6。这就造成了驱动的混乱、冲突、不可用。
1.使用自定义类加载器获取连接

我们通过Class.java的源码其实还可以看到另一个forName重写方法Class.forName(className,initialize,ClassLoader),根据源码可以看到,支持自定义ClassLoader类加载器,只需要我们将Dm6加载到自定义类加载器中,在调用此方法就能手动获取到Dm6的驱动类。

    @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class<?> caller = null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            caller = Reflection.getCallerClass();
            if (sun.misc.VM.isSystemDomainLoader(loader)) {
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        return forName0(name, initialize, loader, caller);
    }

获取到驱动类后,我们还是没有办法使用DriverManager.getConnection()方法获取连接,但是在Driver类的接口中是提供了一个connect获取连接方法,我们可以使用这个connect方法调用到Dm6驱动的connect方法,从而获取连接。

Connection connect(String url, java.util.Properties info) throws SQLException;

自定义类加载器实现需要结合URLClassLoader类,传入jar包的路径即可

// 加载resources/jdbc路径下jar包
ClassPathResource resource = new ClassPathResource("jdbc/DmDriver6.jar");
// jar存在的url,相对路径;绝对路径写法:String jarFilePath = "jar:file:/home/xxx/libs/mysql-connector-java-8.0.29.jar!/";
String jarFilePath = resource.getURL();
//注意类加载器的parent需要设为null,否则它会将加载类通过双亲委派机制委派给AppClassLoader,如果AppClassLoader中已经存在同限定名的类(比如pom文件依赖),这里UrlClassLoader中加载的类会被忽略。
URLClassLoader loader = new URLClassLoader(new URL[]{new URL(jarFilePath)}, null);
兼容步骤
1.将下载DM6的驱动包,并放在resources/jdbc路径下

也可以放在其他目录,放在此resources目录下与WEB-INF/lib目录区分开来

2.手动加载驱动类
String userName = "userName";
String password= "password";
// url
String url = "jdbc:dm:http://127.0.0.1:5236/SYSDBA";
// 驱动名称
String className = "dm.jdbc.dmDriver";
// 加载resources/jdbc路径下jar包
ClassPathResource resource = new ClassPathResource("jdbc/DmDriver6.jar");
// jar存在的url,相对路径;绝对路径写法:String jarFilePath = "jar:file:/home/xxx/libs/mysql-connector-java-8.0.29.jar!/";
String jarFilePath = resource.getURL();
//注意类加载器的parent需要设为null,否则它会将加载类通过双亲委派机制委派给AppClassLoader,如果AppClassLoader中已经存在同限定名的类(比如pom文件依赖),这里UrlClassLoader中加载的类会被忽略。
URLClassLoader loader = new URLClassLoader(new URL[]{new URL(jarFilePath)}, null);
// 获取驱动
Driver driver = (Driver) Class.forName(className, true, loader).newInstance();
// 设置参数
Properties info = new Properties();
info.put("user", userName);
info.put("password", password);
// 获取连接
Connection connection = driver.connect(url, info);

注意:

  • 类加载器的parent需要设为null,否则它会将加载类通过双亲委派机制委派给AppClassLoader,如果AppClassLoader中已经存在同限定名的类(比如pom文件依赖),这里UrlClassLoader中加载的类会被忽略。
  • jar包路径需要添加前缀"jar:file:"与后缀"!/",不然会报错(mysql场景)。在连接其他数据库时,前缀与后缀需要按情况调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值