自定義資料庫連線池實現方式 MySQL
阿新 • • 發佈:2019-01-03
應用程式直接獲取資料庫連線缺點
使用者每次請求都會建立一次資料庫連線,並且資料庫建立連線會消耗相對大的資源和時間。
如果針對於個別的工具或者是大量的程式碼測試甚至系統執行,對資料庫操作次數頻繁,極大的佔用資料庫資源,有可能會發生宕機或者記憶體溢位的現象。
而在大多的專案中,常常用到阿里巴巴開源的資料庫連線池框架,準確來說它不僅僅包括資料庫連線池,
原因其實很簡單,在Spring框架的配置檔案中僅僅一個配置datasource就可以使用Druid了
我們對Druid不加探討,實現自定義的資料庫連線池,來對資料進行處理。
在pom檔案中引人資料庫所需要的jar包,這裡不一一介紹。
建立JdbcConnectionsPool類實現資料來源介面DataSource重寫其getConnection方法
package com.herbert.test.db; import javax.sql.DataSource; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.LinkedList; import java.util.Properties; import java.util.logging.Logger; /** * 實現資料庫連線池 */ public class JdbcConnectionsPool implements DataSource { // /* * 使用靜態塊程式碼,初始化連線池,建立連線池的中最小連結數量連線, * 建立linkedlist集合,將這些連線放入集合中 */ //建立linkedlist集合 private static LinkedList<Connection> linkedlist1 = new LinkedList<Connection>(); public static String GENERATOR_JDBCDRIVER = ""; public static String GENERATOR_JDBCURL = ""; public static String GENERATOR_JDBCUSERNAME = ""; public static String GENERATOR_JDBCPASSWORD = ""; private static int jdbcConnectionInitSize;//最小連線數量 private static int max = 1; //當前最大連線數量=max*jdbcConnectionInitSize private static Properties prop = new Properties(); static { //通過反射機制獲取訪問db.properties檔案 InputStream in = Object.class.getResourceAsStream("/generator.properties"); try { //載入db.properties檔案 prop.load(in); //獲取db.properties檔案中的資料庫連線資訊 GENERATOR_JDBCDRIVER = prop.getProperty("generator.jdbc.driver").trim(); GENERATOR_JDBCURL = prop.getProperty("generator.jdbc.url").trim(); GENERATOR_JDBCUSERNAME = prop.getProperty("generator.jdbc.username").trim(); GENERATOR_JDBCPASSWORD = prop.getProperty("generator.jdbc.password").trim(); jdbcConnectionInitSize = Integer.parseInt(prop.getProperty("jdbcConnectionInitSize")); Class.forName(GENERATOR_JDBCDRIVER); //建立最小連線數個數據庫連線物件以備使用 for (int i = 0; i < jdbcConnectionInitSize; i++) { Connection conn = DriverManager.getConnection(GENERATOR_JDBCURL, GENERATOR_JDBCUSERNAME, GENERATOR_JDBCPASSWORD); System.out.println("獲取到了連結" + conn); //將建立好的資料庫連線物件新增到Linkedlist集合中 linkedlist1.add(conn); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } @Override public Connection getConnection() throws SQLException { //如果集合中沒有資料庫連線物件了,且建立的資料庫連線物件沒有達到最大連線數量,可以再建立一組資料庫連線物件以備使用 if (linkedlist1.size() == 0 && max <= jdbcConnectionInitSize) { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } for (int i = 0; i < jdbcConnectionInitSize; i++) { Connection conn = DriverManager.getConnection(GENERATOR_JDBCURL, GENERATOR_JDBCUSERNAME, GENERATOR_JDBCPASSWORD); System.out.println("獲取到了連結" + conn); //將建立好的資料庫連線物件新增到Linkedlist集合中 linkedlist1.add(conn); } max++; } if (linkedlist1.size() > 0) { //從linkedlist集合中取出一個數據庫連結物件Connection使用 final Connection conn1 = linkedlist1.removeFirst(); System.out.println("linkedlist1資料庫連線池大小是" + linkedlist1.size()); /*返回一個Connection物件,並且設定Connection物件方法呼叫的限制, *當呼叫connection類物件的close()方法時會將Connection物件重新收集放入linkedlist集合中。 */ return (Connection) Proxy.newProxyInstance(JdbcConnectionsPool.class.getClassLoader(),//這裡換成JdbcConnectionsPool.class.getClassLoader();也行 new Class[] { Connection.class }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (!method.getName().equalsIgnoreCase("close")) { return method.invoke(conn1, args); } else { linkedlist1.add(conn1); System.out.println(conn1 + "物件被釋放,重新放回linkedlist集合中!"); System.out.println("此時Linkedlist集合中有" + linkedlist1.size() + "個數據庫連線物件!"); return null; } } }); } else { System.out.println("連線資料庫失敗!"); } return null; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @Override public PrintWriter getLogWriter() throws SQLException { return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { } @Override public void setLoginTimeout(int seconds) throws SQLException { } @Override public int getLoginTimeout() throws SQLException { return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return null; } }
對方法進行進一步封裝
package com.herbert.test.db; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; /** * Created by Herbert on 2018/7/23. */ public class JdbcConnectionPoolUtil { /** * @Field: pool * 資料庫連線池 */ private static JdbcConnectionsPool pool = new JdbcConnectionsPool(); /** * @return Connection資料庫連線物件 * @throws SQLException * @Method: getConnection * @Description: 從資料庫連線池中獲取資料庫連線物件 */ public static Connection getConnection() throws SQLException { return pool.getConnection(); } /** * @param conn * @param st * @param rs * @Method: release * @Description: 釋放資源, * 釋放的資源包括Connection資料庫連線物件,負責執行SQL命令的Statement物件,儲存查詢結果的ResultSet物件 */ public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { //關閉儲存查詢結果的ResultSet物件 rs.close(); } catch (Exception e) { e.printStackTrace(); } rs = null; } if (st != null) { try { //關閉負責執行SQL命令的Statement物件 st.close(); } catch (Exception e) { e.printStackTrace(); } } if (conn != null) { try { //關閉Connection資料庫連線物件 conn.close(); } catch (Exception e) { e.printStackTrace(); } } } }
測試方法
public static void main(String[] args) throws SQLException {
JdbcConnectionPoolUtil jcpt = new JdbcConnectionPoolUtil();
List list = new ArrayList();
Connection c = null;
Statement stmt = null;
try {
c = jcpt.getConnection();
c.setAutoCommit(false);
stmt = c.createStatement();
String sql = "SELECT id FROM test";
PreparedStatement preState = c.prepareStatement(sql);
ResultSet rs = preState.executeQuery();
while (rs.next()) {
String id = rs.getString("id");
list.add(id);
}
jcpt.release(c, stmt, rs);
} catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
System.exit(0);
}
System.out.println("測試:"+list.toString());
}
輸出結果:
獲取到了連結[email protected]
獲取到了連結[email protected]
獲取到了連結[email protected]
獲取到了連結[email protected]
獲取到了連結[email protected]
獲取到了連結[email protected]
獲取到了連結[email protected]
獲取到了連結[email protected]
獲取到了連結[email protected]
獲取到了連結[email protected]
linkedlist1資料庫連線池大小是9
[email protected]物件被釋放,重新放回linkedlist集合中!
此時Linkedlist集合中有10個數據庫連線物件!
測試:[1, 2]
異常處理
java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to java.sql.Connection異常問題解決
程式碼如下
Connection proxy = (Connection) Proxy.newProxyInstance(Connection.class.getClassLoader(),
Connection.class.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if ("close".equals(method.getName())) {
returnConn(conn);
return null;
} else {
return method.invoke(conn, args);
}
}
});
在使用動態代理增強Connection連線物件的close方法時,我碰到了如題所示的異常。通過搜尋我發現這個異常出現的原因在於我使用的mysql資料庫驅動的問題,由於資料庫驅動不同,Connection.class.getInterfaces()返回的結果也不同,它返回的是一個Class[]陣列,然而此陣列的第一個元素必須是Connection才能把建立的代理類轉為Connection物件,否則就會報錯。
所以這裡我們可以採取一個替代方式替換Connection.class.getInterfaces(),即new Class[] { Connection.class },這樣無論資料庫驅動是什麼版本的驅動,都能保證這個型別轉換不出錯。
歡迎關注公眾號