起因
八月初突然接到一個需求,要實現一個工具類。可以動態切換數據源,根據不同的數據源訪問不同的數據庫。
腦海中第一想法使用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");
}