单项目多JDBC驱动版本加载

问题起因

最近项目中需要对已提供数据库钥匙的数据源进行查询,考虑到各方数据库版本不一致,单一jdbc驱动恐无法满足需要,故产生单个项目连接多JDBC驱动版本的想法

知识储备

由于本篇文章涉及到ClassLoader的相关知识,建议未了解过的朋友通过如下博客自行了解
彻底理解ClassLoader

相关资料

实现此功能的过程中参考了如下博文:
动态加载jdbc驱动(可测试jdbc不同版本兼容性)
Java 自定义 ClassLoader 实现隔离运行不同版本jar包的方式

相关代码

实体类:

  • 数据库连接信息实体:
/**
 * 数据库连接信息
 * 重写了equals和hashCode
 * 主要用于后续依赖commons-pool2做外部数据源连接池
 */
public class ConnectionInfoPO {

    /** 连接字符串 */
    private String url;
    /** 连接用户名 */
    private String username;
    /** 连接密码 */
    private String password;

    public ConnectionInfoPO(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    public String getUrl() {
        return url;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) { return true; }
        if (o == null || getClass() != o.getClass()) { return false; }
        ConnectionInfoPO that = (ConnectionInfoPO) o;
        return url.equals(that.url) &&
                username.equals(that.username) &&
                password.equals(that.password);
    }

    @Override
    public int hashCode() {
        return Objects.hash(url, username, password);
    }

}
  • 驱动jar及驱动类相关信息实体:
/**
 * 驱动相关信息
 */
public class DriverInfoPO {

    /** jar文件所在目录的相对路径 - 相对于驱动包存放根路径 */
    private String dirRelativeDirPath;
    /** jar文件名称 */
    private String jarFileName;
    /** 驱动类名称 */
    private String driverClassName;

    /**
     * 未分目录存放驱动包的构造
     * @param jarFileName jar文件名称
     * @param driverClassName 驱动类名称
     */
    public DriverInfoPO(String jarFileName, String driverClassName) {
        this("",jarFileName,driverClassName);
    }

    /**
     * 分目录存放驱动包的构造
     * @param dirRelativeDirPath jar文件所在目录的相对路径 - 相对于驱动包存放根路径
     * @param jarFileName jar文件名称
     * @param driverClassName 驱动类名称
     */
    public DriverInfoPO(String dirRelativeDirPath, String jarFileName, String driverClassName) {
        this.dirRelativeDirPath = dirRelativeDirPath;
        this.jarFileName = jarFileName;
        this.driverClassName = driverClassName;
    }

    public String getDirRelativeDirPath() {
        return dirRelativeDirPath;
    }

    public String getJarFileName() {
        return jarFileName;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

}

工具类:

  • 数据库连接工具类:
/**
 * 数据库连接工具类
 * @author WangQ
 */
public class ConnectionUtil {

    private static final String LOCK = new String("LOCK");
    /** 驱动包存放根目录绝对路径 */
    private static final String driverJarRootDirAbsolutePath = "C:\\Users\\W_WANGQIONG\\Desktop\\drivers";
    /** 驱动对应类加载器集合 key: 驱动包路径:驱动类名称 value: urlClassLoader实例 */
    private static Map<String, URLClassLoader> urlClassLoaderMap = new ConcurrentHashMap<>();

    /**
     * 创建数据库连接
     * @param driverInfoPO 驱动信息
     * @param connectionInfoPO 连接信息
     * @return 数据库连接
     * @throws Exception 获取DB连接失败
     */
    public static Connection create(DriverInfoPO driverInfoPO, ConnectionInfoPO connectionInfoPO) throws Exception {
        String jarFilePath = driverJarRootDirAbsolutePath + File.separator + driverInfoPO.getDirRelativeDirPath() + File.separator + driverInfoPO.getJarFileName();
        String key = String.format("%s:%s",jarFilePath, driverInfoPO.getDriverClassName());

        URLClassLoader loader = null;
        synchronized (LOCK) {
            loader = urlClassLoaderMap.get(key);

            if(loader == null){
                URL url = new URL("jar:file:" + jarFilePath + "!/");
                loader = new URLClassLoader(new URL[]{url});
                urlClassLoaderMap.put(key,loader);
            }
        }

        // 记录原始ClassLoader并设置当前线程的ClassLoader
        ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(loader);

        // 获取数据库连接
        Driver driver = (Driver) loader.loadClass(driverInfoPO.getDriverClassName()).newInstance();
        Properties connInfo = new Properties();
        connInfo.put("user", connectionInfoPO.getUsername());
        connInfo.put("password", connectionInfoPO.getPassword());
        Connection conn = driver.connect(connectionInfoPO.getUrl(), connInfo);

        // 还原原始ClassLoader
        Thread.currentThread().setContextClassLoader(oldClassLoader);

        return conn;
    }

    /**
     * 关闭数据库连接
     */
    public static void close(Connection conn){
        try {
            if(conn != null){
                conn.close();
            }
        } catch (SQLException throwables) {
            // ignore
        }
    }

}

测试:

相关环境版本:

  • jdk: 1.8.0_251
  • mysql: 8.0.21
  • jdbc驱动jar: v3.1.14、v5.0.8、v5.1.47、v8.0.16

单jdbc驱动测试:

通过替换当前项目的jar包,获取DB连接得出各版本驱动连接8.0.21的结果

public class SingleDriverTest {

    public static void main(String[] args) throws ClassNotFoundException, SQLException {
        Class.forName("com.mysql.jdbc.Driver");

        DriverManager.getConnection("jdbc:mysql://localhost:3306/demo","root","123456");

        /*
        v3.1.14:
            Exception in thread "main" java.sql.SQLException: Client does not support authentication protocol requested by server; consider upgrading MySQL client
            at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2975)
            at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:798)
            at com.mysql.jdbc.MysqlIO.secureAuth411(MysqlIO.java:3700)
            at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1203)
            at com.mysql.jdbc.Connection.createNewIO(Connection.java:2572)
            at com.mysql.jdbc.Connection.<init>(Connection.java:1485)
            at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:266)
            at java.sql.DriverManager.getConnection(DriverManager.java:664)
            at java.sql.DriverManager.getConnection(DriverManager.java:247)
            at pub.ikkyu.test.SingleDriverTest.main(SingleDriverTest.java:14)
         */

        /*
        v5.0.8:
            Exception in thread "main" com.mysql.jdbc.exceptions.MySQLNonTransientConnectionException: Client does not support authentication protocol requested by server; consider upgrading MySQL client
            at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:921)
            at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2985)
            at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:885)
            at com.mysql.jdbc.MysqlIO.secureAuth411(MysqlIO.java:3421)
            at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1247)
            at com.mysql.jdbc.Connection.createNewIO(Connection.java:2775)
            at com.mysql.jdbc.Connection.<init>(Connection.java:1555)
            at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:285)
            at java.sql.DriverManager.getConnection(DriverManager.java:664)
            at java.sql.DriverManager.getConnection(DriverManager.java:247)
            at pub.ikkyu.test.SingleDriverTest.main(SingleDriverTest.java:14)
         */

        /*
        v5.1.47:
            Tue Sep 01 11:40:54 CST 2020 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
         */

        /*
        v8.0.16
            Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
         */
    }
}

多版本驱动测试:

public class MulitJdbcVersionTest {

    private static final Map<String,String> conns = new HashMap<String, String>();

    static {
        conns.put("C:\\work\\projects\\dspool\\lib\\mysql-connector-java-3.1.14.jar", "com.mysql.jdbc.Driver");
        conns.put("C:\\work\\projects\\dspool\\lib\\mysql-connector-java-5.0.8.jar", "com.mysql.jdbc.Driver");
        conns.put("C:\\work\\projects\\dspool\\lib\\mysql-connector-java-5.1.47.jar", "com.mysql.jdbc.Driver");
        conns.put("C:\\work\\projects\\dspool\\lib\\mysql-connector-java-8.0.16.jar", "com.mysql.jdbc.Driver");
    }

    public static void main(String[] args) {
        ConnectionInfoPO connectionInfoPO = new ConnectionInfoPO("jdbc:mysql://localhost:3306/amms","root","wq968187");

        DriverInfoPO driverInfoPO = new DriverInfoPO("mysql-connector-java-3.1.14.jar","com.mysql.jdbc.Driver");
        try {
            Connection conn = ConnectionUtil.create(driverInfoPO, connectionInfoPO);
            System.out.println("mysql-connector-java-3.1.14: "+conn);
            ConnectionUtil.close(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
        driverInfoPO = new DriverInfoPO("mysql-connector-java-5.0.8.jar","com.mysql.jdbc.Driver");
        try {
            Connection conn = ConnectionUtil.create(driverInfoPO, connectionInfoPO);
            System.out.println("mysql-connector-java-5.0.8: "+conn);
            ConnectionUtil.close(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
        driverInfoPO = new DriverInfoPO("mysql-connector-java-5.1.47.jar","com.mysql.jdbc.Driver");
        try {
            Connection conn = ConnectionUtil.create(driverInfoPO, connectionInfoPO);
            System.out.println("mysql-connector-java-5.1.47: "+conn);
            ConnectionUtil.close(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
        driverInfoPO = new DriverInfoPO("mysql-connector-java-8.0.16.jar","com.mysql.jdbc.Driver");
        try {
            Connection conn = ConnectionUtil.create(driverInfoPO, connectionInfoPO);
            System.out.println("mysql-connector-java-8.0.16: "+conn);
            ConnectionUtil.close(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
	
	/*
	 * 
	 * java.sql.SQLException: Client does not support authentication protocol requested by server; consider upgrading MySQL client
	 * at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2975)
	 * at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:798)
	 * at com.mysql.jdbc.MysqlIO.secureAuth411(MysqlIO.java:3700)
	 * at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1203)
	 * at com.mysql.jdbc.Connection.createNewIO(Connection.java:2572)
	 * at com.mysql.jdbc.Connection.<init>(Connection.java:1485)
	 * at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:266)
	 * at pub.ikkyu.utils.ConnectionUtil.create(ConnectionUtil.java:57)
	 * at pub.ikkyu.pool.DatabaseConnectionPooledObjectFactory.create(DatabaseConnectionPooledObjectFactory.java:28)
	 * at pub.ikkyu.pool.DatabaseConnectionPooledObjectFactory.create(DatabaseConnectionPooledObjectFactory.java:15)
	 * at org.apache.commons.pool2.BaseKeyedPooledObjectFactory.makeObject(BaseKeyedPooledObjectFactory.java:62)
	 * at org.apache.commons.pool2.impl.GenericKeyedObjectPool.create(GenericKeyedObjectPool.java:1041)
	 * at org.apache.commons.pool2.impl.GenericKeyedObjectPool.borrowObject(GenericKeyedObjectPool.java:357)
	 * at org.apache.commons.pool2.impl.GenericKeyedObjectPool.borrowObject(GenericKeyedObjectPool.java:279)
	 * at pub.ikkyu.test.DatabaseConnectionPoolTest.main(DatabaseConnectionPoolTest.java:41)
com.mysql.jdbc.exceptions.MySQLNonTransientConnectionException: Client does not support authentication protocol requested by server; consider upgrading MySQL client
	 * at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:921)
	 * at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:2985)
	 * at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:885)
	 * at com.mysql.jdbc.MysqlIO.secureAuth411(MysqlIO.java:3421)
	 * at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1247)
	 * at com.mysql.jdbc.Connection.createNewIO(Connection.java:2775)
	 * at com.mysql.jdbc.Connection.<init>(Connection.java:1555)
	 * at com.mysql.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:285)
	 * at pub.ikkyu.utils.ConnectionUtil.create(ConnectionUtil.java:57)
	 * at pub.ikkyu.pool.DatabaseConnectionPooledObjectFactory.create(DatabaseConnectionPooledObjectFactory.java:28)
	 * at pub.ikkyu.pool.DatabaseConnectionPooledObjectFactory.create(DatabaseConnectionPooledObjectFactory.java:15)
	 * at org.apache.commons.pool2.BaseKeyedPooledObjectFactory.makeObject(BaseKeyedPooledObjectFactory.java:62)
	 * at org.apache.commons.pool2.impl.GenericKeyedObjectPool.create(GenericKeyedObjectPool.java:1041)
	 * at org.apache.commons.pool2.impl.GenericKeyedObjectPool.borrowObject(GenericKeyedObjectPool.java:357)
	 * at org.apache.commons.pool2.impl.GenericKeyedObjectPool.borrowObject(GenericKeyedObjectPool.java:279)
	 * at pub.ikkyu.test.DatabaseConnectionPoolTest.main(DatabaseConnectionPoolTest.java:41)
     * Thu Sep 03 13:37:14 CST 2020 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.
com.mysql.jdbc.JDBC4Connection@7d9f158f
     * Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
com.mysql.cj.jdbc.ConnectionImpl@46268f08
	 */
}

注意点:

整个工具类代码最核心的部分如下:

// 保留原始ClassLoader
ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(loader);

// 还原原始ClassLoader
Thread.currentThread().setContextClassLoader(oldClassLoader);

如果不设置当前线程的ClassLoader,则可能会出现ClassLoader默认为AppClassLoader,导致相同全限定名的类只会加载一次,从而达不到预期的目标的问题

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值