記:JDBC動態切換數據源,連接不同的數據庫

起因

八月初突然接到一個需求,要實現一個工具類。可以動態切換數據源,根據不同的數據源訪問不同的數據庫。
腦海中第一想法使用JDBC去實現,那麼說幹就幹。

經過

首先我們先來回憶一下JDBC連接。(沒錯,我已經忘了怎麼寫了。手動捂臉)

1.加載數據驅動
2.建立數據連接對象
3.創建Statement對象
4.執行sql,得到ResultSet對象
5.獲取數據
6.關閉連接

’Talk is cheap,show me the code’

		String driver = "com.mysql.jdbc.Driver";
        String url = "jdbc:mysql://localhost:3306/data_test";
        String username = "root";
        String password = "123456";

        String sql = "SELECT * FROM S_STUDENT;";

        try {
            // 1.加載數據庫驅動
            Class.forName(driver);
            // 2.建立數據庫連接對象
            Connection connection = DriverManager.getConnection(url, username, password);
            // 3.創建Statement對象
            Statement statement = connection.createStatement();
            // 4.執行sql,得到ResultSet對象
            ResultSet resultSet = statement.executeQuery(sql);
            // 5.獲取數據,操作數據
            while (resultSet.next()) {
                String name = resultSet.getString("name");
                int age = resultSet.getInt("age");
                System.out.println("name = " + name + "; age = " + age);
            }
            // 6.釋放數據
            resultSet.close();
            statement.close();
            connection.close();
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

以上就是一個簡單的JDBC連接小例子。好了,下面我們開始改造。

改造代碼

核心要求:動態切換數據源
拿上面的例子來說,要動態切換數據庫驅動,同時動態的切換數據庫連接信息。

思路分析:
1.動態傳入數據庫驅動和數據庫連接信息。
2.定義構造方法爲對應的屬性進行初始化,從而來實現動態屬性的賦值。
3.其他操作無大變動。

代碼改造~~~

改造的代碼就不貼出來了,會在最後貼一個最終版本。

問題浮現

改造完代碼之後進行測試發現,並沒有動態的切換數據庫驅動。

排查代碼以及設計邏輯,無果。

打開瀏覽器,開始面向Google編程。

直到搜到這篇博客,深入淺出的講解了一下JDBC的驅動加載。鏈接奉上,感謝博主分享

在搜索的時候下面這篇博客寫的也不錯,這裏我也分享出來。大家可以看一看。
鏈接奉上,感謝博主分享

結果

看過上面的博客之後,瞭解到DriverManager負責註冊和註銷數據庫驅動。使用DriverManager.getConnection()獲取連接對象時,會遍歷其維護的Driver信息。然後調用acceptsURL(url)方法判斷當前驅動是否打開該URL連接,如果該方法返回true,則返回對應的Driver。 好的瞭解到這,明白了原來可以使用static代碼塊將數據庫驅動全部交由DriverManager維護。連接時只需要傳入對應數據源的url、username、password就可以了。改造代碼如下:

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * JDBC 工具類
 *
 * @author 山楂罐頭
 * @date 2019/8/6
 */
public class JdbcUtil implements Serializable {
    private static final long serialVersionUID = 1382377502744233953L;
    private static final Logger logger = LoggerFactory.getLogger(JdbcUtil.class);

    // 數據庫連接對象
    private Connection connection = null;
    // 執行靜態SQL語句
    private Statement statement = null;
    // 執行動態SQL語句
    private PreparedStatement preparedStatement = null;
    // 結果集
    private ResultSet resultSet = null;


    // 數據庫連接URL
    private String url;
    // 用戶名
    private String username;
    // 密碼
    private String password;

    static {
        // 加載數據庫驅動。注:我將數據源驅動放入到了一個枚舉類中,此處是從枚舉類中獲取。
        for (DB_DRIVER_ENUM dbDriverEnum : DB_DRIVER_ENUM.values()) {
            try {
                // 加載數據驅動,需確保引入相關的jar包
                Class.forName(dbDriverEnum.getDataSourceDriver());
            } catch (ClassNotFoundException e) {
                logger.error("\r\n 初始化 JdbcUtil 數據驅動失敗!失敗的數據驅動類型:" + dbDriverEnum.getDataSourceDriver());
                e.printStackTrace();
            }
        }
    }

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

    /**
     * 獲取數據庫連接
     *
     * @return 數據庫連接
     */
    private Connection getConnection() {
        try {
            // 建立數據庫連接對象
            connection = DriverManager.getConnection(url, username, password);
        } catch (SQLException e) {
            logger.error("\r\n JdbcUtil 連接數據庫失敗!失敗原因:" + ExceptionUtils.getStackTrace(e));
            e.printStackTrace();
        }
        return connection;
    }

    /**
     * 執行查詢操作
     *
     * @param sql SQL語句
     * @return 返回值是一個結果集
     */
    private ResultSet executeQuery(String sql) {
        try {
            connection = this.getConnection();
            if (null != connection) {
                statement = connection.createStatement();
                if (null != statement) {
                    resultSet = statement.executeQuery(sql);
                }
            }
        } catch (SQLException e) {
            logger.error("\r\n JdbcUtil 執行查詢SQL失敗!失敗原因:" + ExceptionUtils.getStackTrace(e));
            e.printStackTrace();
        }
        return resultSet;
    }

    /**
     * 關閉數據庫連接
     */
    private void close() {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                logger.error("\r\n JdbcUtil 關閉結果集失敗!失敗原因:" + ExceptionUtils.getStackTrace(e));
                e.printStackTrace();
            }
        }
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                logger.error("\r\n JdbcUtil 關閉執行靜態SQL實例對象失敗!失敗原因:" + ExceptionUtils.getStackTrace(e));
                e.printStackTrace();
            }
        }
        if (preparedStatement != null) {
            try {
                preparedStatement.close();
            } catch (SQLException e) {
                logger.error("\r\n JdbcUtil 關閉執行動態SQL實例對象失敗!失敗原因:" + ExceptionUtils.getStackTrace(e));
                e.printStackTrace();
            }
        }
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                logger.error("\r\n JdbcUtil 關閉數據庫連接失敗!失敗原因:" + ExceptionUtils.getStackTrace(e));
                e.printStackTrace();
            }
        }
    }

    /**
     * 處理resultSet結果集對象,並將結果集對象封裝成 List<Map<String, Object>> 對象
     *
     * @param resultSet 結果集對象
     * @return 結果集合
     * @throws SQLException SQL異常
     */
    private static List<Map<String, Object>> getDates(ResultSet resultSet) throws SQLException {
        List<Map<String, Object>> dates = new ArrayList<>();
        // 獲取結果集的數據結構對象
        ResultSetMetaData metaData = resultSet.getMetaData();
        while (resultSet.next()) {
            Map<String, Object> rowMap = new HashMap<>();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                rowMap.put(metaData.getColumnName(i), resultSet.getObject(i));
            }
            dates.add(rowMap);
        }
        return dates;
    }


    /* *************************** 以下爲提供外部調用者的調用方法 ************************************/

    /**
     * 測試數據源是否可以連通
     *
     * @return 連通結果
     */
    public Boolean testDBConnect() {
        if (this.getConnection() != null) {
            // 關閉相關連接
            this.close();
            return true;
        } else {
            return false;
        }
    }

    /**
     * 執行查詢語句 - 無參數
     *
     * @param querySql 查詢語句
     * @return 結果集合
     */
    public List<Map<String, Object>> executeQuerySql(String querySql) {
        try {
            // 執行查詢語句
            ResultSet rs = this.executeQuery(querySql);
            if (null != rs) {
                // 處理查詢結果
                return getDates(rs);
            }
        } catch (SQLException e) {
            logger.error("\r\n JdbcUtil 執行executeQuerySql()失敗!失敗原因:" + ExceptionUtils.getStackTrace(e));
            e.printStackTrace();
        } finally {
            // 關閉相關連接
            this.close();
        }
        return null;
    }
}

至此,工具類就初步成型了。調用方式爲:

// 檢查數據源是否可以連通
JdbcUtil jdbcUtil = new JdbcUtil(url, userName, password);
jdbcUtil.testDBConnect();

寫在最後

在面向Google編程的時候,我發現了好多coder都寫了JDBC的連接工具。這裏給大家推薦一個個人感覺比較好的工具類。有需要的小夥伴猛戳這裏

相關源碼

以下是DriverManager的相關源碼,以對上面內容的補充和加深理解。

    // List of registered JDBC drivers
    private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
    
    // ...省略部分源碼...

    @CallerSensitive
    public static Connection getConnection(String url,
        String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();

        if (user != null) {
            info.put("user", user);
        }
        if (password != null) {
            info.put("password", password);
        }

        return (getConnection(url, info,Reflection.getCallerClass()));
    }
    
    //  ...省略部分源碼...
    
    @CallerSensitive
    public static Driver getDriver(String url)
        throws SQLException {

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

        Class<?> callerClass = Reflection.getCallerClass();

        // Walk through the loaded registeredDrivers attempting to locate someone
        // who understands the given URL.
        for (DriverInfo aDriver : registeredDrivers) {
            // If the caller does not have permission to load the driver then
            // skip it.
            if(isDriverAllowed(aDriver.driver, callerClass)) {
                try {
                    if(aDriver.driver.acceptsURL(url)) {
                        // Success!
                        println("getDriver returning " + aDriver.driver.getClass().getName());
                    return (aDriver.driver);
                    }

                } catch(SQLException sqe) {
                    // Drop through and try the next driver.
                }
            } else {
                println("    skipping: " + aDriver.driver.getClass().getName());
            }

        }

        println("getDriver: no suitable driver");
        throw new SQLException("No suitable driver", "08001");
    }
    
    //  ...省略部分源碼...
    
    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");
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章