一、什麼是JDBC連接池? 在傳統的JDBC連接中,每次獲得一個Connection連接都需要載入通過一些繁雜的代碼去獲取,例如以下代碼: 這樣繁雜的操作只為了獲取一次連接,當然,我們可以將其封裝成一個工具類來訪問(上圖以封裝好Connection的連接),但是每一次連接都需要取載入一次是不是很浪費 ...
一、什麼是JDBC連接池?
在傳統的JDBC連接中,每次獲得一個Connection連接都需要載入通過一些繁雜的代碼去獲取,例如以下代碼:
public static Connection getConn(){ Connection conn = null; String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "root"; try { Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection(url, user, password); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return conn; }
這樣繁雜的操作只為了獲取一次連接,當然,我們可以將其封裝成一個工具類來訪問(上圖以封裝好Connection的連接),但是每一次連接都需要取載入一次是不是很浪費性能,為了優化性能,那麼就出現了連接池。
連接池在初始化的時候就創建了幾個連接供我們使用,當我們需要連接時只需要從連接池中獲取已存在的連接,當初始化的幾個連接都沒有時,會重新創建一個連接,使用完連接後不會去銷毀連接,而是歸還給連接池供後面需要連接的使用。(當然,連接池不僅僅只是這麼簡單,這裡就只做這些介紹)
常用的連接池有DBCP、C3P0,現在最主流是好像是阿裡的Druid連接池,還有tomcat的自帶的JNDI連接池
二、自定義一個簡單的連接池
對自定義連接池的分析:
1.2.因為是連接池 ,我們需要實現DataSource介面,並實現其中的方法,基於我們的情況,我們關於與getConnection()方法;
2.既然要存放幾個連接對象,那麼我們用一個集合來存放它,基於會經常操作增加和刪除那麼選用LinkedList;
3.連接的銷毀並不是銷毀連接,而是將連接歸還給連接池
編碼:
1.創建一個類MyDataSource 並實現DataSource介面
當此類載入時它就需要有一個容器來存放Connection,所以定義一個靜態的屬性:
private static List<Connection> connectionList = new LinkedList<>();
2.因為需要取獲得資料庫連接,所以我們封裝一個獲取資料庫連接的方法
public Connection getOneConnection(){ Connection conn = null; try{
//此處通過外部的properties文件來獲取的數據,這樣更加靈活。 InputStream in = MyDataSource.class.getClassLoader(). getResourceAsStream("jdbc/jdbc.properties"); Properties pro = new Properties(); pro.load(in); driver = pro.getProperty("driver"); url = pro.getProperty("url"); username = pro.getProperty("user"); password = pro.getProperty("password"); Class.forName(driver); conn = DriverManager.getConnection(url,username,password); }catch (Exception e){ e.getStackTrace(); } return conn; }
註意的是我這裡通過propertie文件的獲取的數據,可根據實際情況來選擇
3.初始化幾個連接放入容器中。可以使用靜態代碼塊來實現,但是如果沒有使用此數據源那麼就造成了資源的浪費,所以我考慮將初始化幾個連接的實現放到他的構造方法中,即當需要此連接池的時候他才會隨之創建幾個連接。如下:
public MyDataSource() { for (int i = 0; i < 5; i++) { Connection conn = getOneConnection();//調用創建連接的方法 connectionList.add(conn); } }
4.現在開始重寫外部從此連接池中獲取連接的方法getConnection()
@Override public Connection getConnection() throws SQLException { Connection conn = null; if(connectionList == null || connectionList.size() <= 0){ Connection connection = getConnection(); connectionList.add(connection); } conn = connectionList.remove(0); return conn; }
5.創建一個對象返回的方法,即將用完的連接放入歸還到連接池中
public void backConnection(Connection conn){ connectionList.add(conn); }
OK,這樣就完成了一個簡單的自定義連接池,測試代碼如下:
public static void main( String[] args ) throws SQLException { MyDataSource dataSource = new MyDataSource(); Connection conn = dataSource.getConnection(); String sql = "select * from user where u_id = ?"; PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(sql); ps.setInt(1, 1); rs = ps.executeQuery(); while (rs.next()) { System.out.println("id="+rs.getInt(1)); System.out.println("username="+rs.getString(2)); System.out.println("password="+rs.getString(3)); } } catch (SQLException e) { e.printStackTrace(); } finally { dataSource.backConnection(conn); } }
因為忽略,我的代碼中沒有關閉其他兩個對象。
現在有一個小問題就是,我們的關閉連接是通過連接池的方法來實現的,但是,如果用戶調用Connection對象的close方法,那麼連接時被銷毀了,並沒有返回給連接池,那麼我們來優化它,讓用戶使用close()方法不會去銷毀連接,而是去歸還連接。
方案有很多中,這裡採用裝飾著模式的一種。
優化:
1.新建一個類MyConnection來實現Connection介面,其中他有屬性類型為Connection conn和一個Liis<Connection>。
private Connection conn; private List<Connection> pool; public MyConnection(Connection conn, List<Connection> pool) { this.conn = conn; this.pool = pool; }
2.然後實現介面的close方法
@Override public void close() throws SQLException { System.out.println("回收連接"); pool.add(conn); }
3.然後實現他獲取Statement的方法,如果不實現那麼獲取此Statement會出現空指針錯誤,我這裡就只實現了PreparedStatement的獲取方法
@Override public PreparedStatement prepareStatement(String sql) throws SQLException { System.out.println("獲得Statement"); return conn.prepareStatement(sql); }
4.然後刪除掉MyDataSource類中歸還連接的方法backConnection,並將構造方法和獲取連接的方法做如下改造
public MyDataSource2() { for (int i = 0; i < 5; i++) { Connection conn = getOneConnection(); MyConnection myConn = new MyConnection(conn, connectionList); connectionList.add(myConn); } }
@Override public Connection getConnection() throws SQLException { Connection conn = null; if(connectionList == null || connectionList.size() <= 0){ Connection connection = getConnection(); MyConnection myConn = new MyConnection(connection, connectionList); connectionList.add(myConn); } conn = connectionList.remove(0); return conn; }
好了,這樣用戶直接調用我們的Connection的close方法就不會去銷毀連接了,會正確的歸還給了連接池了,對測試代碼稍做修改即可測試。