シングルプロジェクトマルチJDBCドライババージョンロード

44322 ワード

問題の原因
最近のプロジェクトでは、データベースキーが提供されているデータソースを問い合わせる必要があり、各方面のデータベースバージョンが一致していないことを考慮して、単一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:v 3.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.(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.(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.(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.(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回しかロードされず、予期したターゲットに達しないという問題が発生する可能性があります.