JDBC整合c3p0資料庫連線池 解決Too many connections錯誤
前段時間,接手一個專案使用的是原始的jdbc作為資料庫的訪問,釋出到伺服器上在運行了一段時間之後總是會出現無法訪問的情況,登入到伺服器,檢視tomcat日誌發現總是報如下的錯誤。
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Data source rejected establishment of connection, message from server: "Too many connections" at sun.reflect.GeneratedConstructorAccessor43.newInstance(Unknown Source) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:526) at com.mysql.jdbc.Util.handleNewInstance(Util.java:406) at com.mysql.jdbc.Util.getInstance(Util.java:381) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:984) at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956) at com.mysql.jdbc.MysqlIO.doHandshake(MysqlIO.java:1095) at com.mysql.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:2181) ... 32 more com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up.
Data source rejected establishment of connection, message from server: "Too many connections"
通過對這句話的分析可以得知,是因為資料庫的連線太多了,資料庫連線被拒絕了,導致了專案的無法訪問。
於是先檢視MySql最大連線數
show variables like "max_connections";
顯示當前正在執行的MySql連線
show processlist ;
通過這兩個資料的對比,發現MySql的連線數居然滿了,於是修改了MySql的最大連線數至2000,重啟專案之後發現一切正常,過了一段時間之後,繼續查詢MySql的連線狀態,發現MySql的連線數不停的在飆升,不一會的功夫連線數又滿了,這時候,我開始意識到是專案的程式碼出現了問題,於是我開始審查程式碼,發現專案中使用的JdbcUtils工具類並不是單例模式,在每次使用JdbcUtils的時候都會new一個JdbcUtils物件,在建立物件的時候會使用java.sql.Connection建立連線,但是在使用完JdbcUtils的時候,我們並沒有呼叫Connection的close()方法,這樣導致使用jdbc連線資料庫的時候會導致連線數越來越多,然而沒用的連線數卻沒有釋放掉,最終到時資料庫連線報錯,專案無法使用資料庫了。
於是我覺得使用資料庫連線池來整合jdbc,連線池的介紹如下:
對於共享資源,有一個很著名的設計模式:資源池(Resource Pool)。該模式正是為了解決資源的頻繁分配﹑釋放所造成的問題。為解決我們的問題,可以採用資料庫連線池技術。資料庫連線池的基本思想就是為資料庫連線建立一個“緩衝池”。預先在緩衝池中放入一定數量的連線,當需要建立資料庫連線時,只需從“緩衝池”中取出一個,使用完畢之後再放回去。我們可以通過設定連線池最大連線數來防止系統無盡的與資料庫連線。更為重要的是我們可以通過連線池的管理機制監視資料庫的連線的數量﹑使用情況,為系統開發﹑測試及效能調整提供依據。
傳統的獲取連線方式如下圖所示:
使用者每次請求都需要向資料庫獲得連結,而資料庫建立連線通常需要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,資料庫伺服器就需要建立10萬次連線,極大的浪費資料庫的資源,並且極易造成資料庫伺服器記憶體溢位、拓機。
採用連線池技術後的過程如下:
資料庫連線是一種關鍵的有限的昂貴的資源,這一點在多使用者的網頁應用程式中體現的尤為突出。對資料庫連線的管理能顯著影響到整個應用程式的伸縮性和健壯性,影響到程式的效能指標。資料庫連線池負責分配,管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是重新建立一個。
C3P0連線池
c3p0是一個開源的JDBC連線池,它實現了資料來源和JNDI繫結,支援JDBC3規範和JDBC2的標準擴充套件。c3p0一般是與Hibernate,Spring等框架一塊使用的,當然也可以單獨使用。
dbcp沒有自動回收空閒連線的功能,c3p0有自動回收空閒連線功能。
使用c3p0需要匯入c3p0.jar、mchange-commons-.jar,如果操作的是Oracle資料庫,那麼還需要匯入c3p0-oracle-thin-extras-pre1.jar。
下面開始使用C3P0整合JDBC
資料庫配置:
#Oracle Config
#jdbc.driver=oracle.jdbc.driver.OracleDriver
#jdbc.url=jdbc:oracle:thin:@localhost:1521:ora9i
#jdbc.username=qq
#jdbc.pwd=qq
#MySQL Config
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=jdbctest
jdbc.pwd=123456
DBUtils,初始化連線池配置,設定資料庫的最大連線數和最小連線數
package hn.veryedu.jdbc.common.db; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.HashMap; import java.util.Map; import java.util.Properties; import javax.sql.DataSource; import com.mchange.v2.c3p0.DataSources; public class DBUtils { private static String url = null; private static String username = null; private static String pwd = null; private static DataSource ds_pooled; /** * 載入資料庫連線的配置檔案和驅動 */ static{ FileInputStream fis = null; Properties env = new Properties(); try { fis = new FileInputStream("dbconfig.properties"); //載入屬性檔案中的資料庫配置資訊 //以=左邊作為key值,右邊作為value值 env.load(fis); //1. 載入驅動類 Class.forName(env.getProperty("jdbc.driver")); url = env.getProperty("jdbc.url"); username = env.getProperty("jdbc.username"); pwd = env.getProperty("jdbc.pwd"); //設定連線資料庫的配置資訊 DataSource ds_unpooled = DataSources .unpooledDataSource(url, username, pwd); Map<String, Object> pool_conf = new HashMap<String, Object>(); //設定最大連線數 pool_conf.put("maxPoolSize", 20); //連線池應該保有的最小連線的數量 pool_conf.put("minPoolSize", 2); //初始化連線池時,獲取的連線個數 pool_conf.put("initialPoolSize", 10); //當連線池中已經沒有連線時,連線池自動獲取連線時一次獲取的連線個數 pool_conf.put("acquireIncrement", 3); ds_pooled = DataSources.pooledDataSource(ds_unpooled, pool_conf); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } /** * 獲取連線物件 */ public static Connection getConnection() { // 2. 設定連線的url,username,pwd Connection connection = null; try { connection = ds_pooled.getConnection(); //connection.prepareStatement("set names utf8mb4").executeQuery(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return connection; } /** * 釋放連線池資源 */ public static void clearup(){ if(ds_pooled != null){ try { DataSources.destroy(ds_pooled); } catch (SQLException e) { e.printStackTrace(); } } } /** * 資源關閉 * * @param rs * @param stmt * @param conn */ public static void close(ResultSet rs, Statement stmt , Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
然後是JdbcUtils查詢各種資料List、Map等,通過DBUtils獲取資料庫連線,使用完成之後釋放所有的連線
package hn.veryedu.jdbc.common.db; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class JdbcUtils { private Connection connection; private PreparedStatement pstmt; private ResultSet resultSet; /** * 獲得資料庫的連線 * @return * @throws SQLException */ public Connection getConnection() throws SQLException{ connection = DBUtils.getConnection(); //如果資料庫支援utf8mb4 建立連線後需要使用下面的程式碼 //connection.prepareStatement("set names utf8mb4").executeQuery(); return connection; } /** * 增加、刪除、改 * @param sql * @param params * @return * @throws SQLException */ public boolean updateByPreparedStatement(String sql, List<Object> params)throws SQLException{ boolean flag = false; int result = -1; this.getConnection(); pstmt = connection.prepareStatement(sql); int index = 1; if(params != null && !params.isEmpty()){ for(int i=0; i<params.size(); i++){ pstmt.setObject(index++, params.get(i)); } } result = pstmt.executeUpdate(); flag = result > 0 ? true : false; this.releaseConn(); return flag; } /** * 查詢單條記錄 * @param sql * @param params * @return * @throws SQLException */ public Map<String, Object> findSimpleResult(String sql, List<Object> params) throws SQLException{ Map<String, Object> map = new HashMap<String, Object>(); int index = 1; this.getConnection(); pstmt = connection.prepareStatement(sql); if(params != null && !params.isEmpty()){ for(int i=0; i<params.size(); i++){ pstmt.setObject(index++, params.get(i)); } } resultSet = pstmt.executeQuery();//返回查詢結果 ResultSetMetaData metaData = resultSet.getMetaData(); int col_len = metaData.getColumnCount(); while(resultSet.next()){ for(int i=0; i<col_len; i++ ){ String cols_name = metaData.getColumnName(i+1); Object cols_value = resultSet.getObject(cols_name); if(cols_value == null){ cols_value = ""; } map.put(cols_name, cols_value); } } this.releaseConn(); return map; } /** * 查詢多條記錄 * @param sql * @param params * @return * @throws SQLException */ public List<Map<String, Object>> findModeResult(String sql, List<Object> params) throws SQLException{ List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); int index = 1; this.getConnection(); pstmt = connection.prepareStatement(sql); if(params != null && !params.isEmpty()){ for(int i = 0; i<params.size(); i++){ pstmt.setObject(index++, params.get(i)); } } resultSet = pstmt.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); int cols_len = metaData.getColumnCount(); while(resultSet.next()){ Map<String, Object> map = new HashMap<String, Object>(); for(int i=0; i<cols_len; i++){ String cols_name = metaData.getColumnName(i+1); Object cols_value = resultSet.getObject(cols_name); if(cols_value == null){ cols_value = ""; } map.put(cols_name, cols_value); } list.add(map); } this.releaseConn(); return list; } /** * 通過反射機制查詢單條記錄 * @param sql * @param params * @param cls * @return * @throws Exception */ public <T> T findSimpleRefResult(String sql, List<Object> params, Class<T> cls )throws Exception{ T resultObject = null; int index = 1; this.getConnection(); pstmt = connection.prepareStatement(sql); if(params != null && !params.isEmpty()){ for(int i = 0; i<params.size(); i++){ pstmt.setObject(index++, params.get(i)); } } resultSet = pstmt.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); int cols_len = metaData.getColumnCount(); while(resultSet.next()){ //通過反射機制建立一個例項 resultObject = cls.newInstance(); for(int i = 0; i<cols_len; i++){ String cols_name = metaData.getColumnName(i+1); Object cols_value = resultSet.getObject(cols_name); if(cols_value == null){ cols_value = ""; } Field field = cls.getDeclaredField(cols_name); field.setAccessible(true); //開啟javabean的訪問許可權 field.set(resultObject, cols_value); } } this.releaseConn(); return resultObject; } /** * 通過反射機制查詢多條記錄 * @param sql * @param params * @param cls * @return * @throws Exception */ public <T> List<T> findMoreRefResult(String sql, List<Object> params, Class<T> cls )throws Exception { List<T> list = new ArrayList<T>(); int index = 1; this.getConnection(); pstmt = connection.prepareStatement(sql); if(params != null && !params.isEmpty()){ for(int i = 0; i<params.size(); i++){ pstmt.setObject(index++, params.get(i)); } } resultSet = pstmt.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); int cols_len = metaData.getColumnCount(); while(resultSet.next()){ //通過反射機制建立一個例項 T resultObject = cls.newInstance(); for(int i = 0; i<cols_len; i++){ String cols_name = metaData.getColumnName(i+1); Object cols_value = resultSet.getObject(cols_name); if(cols_value == null){ cols_value = ""; } Field field = cls.getDeclaredField(cols_name); field.setAccessible(true); //開啟javabean的訪問許可權 field.set(resultObject, cols_value); } list.add(resultObject); } this.releaseConn(); return list; } /** * 返回單個結果值,如count\min\max等 * * @param sql * sql語句 * @param paramters * 引數列表 * @return 結果 * @throws SQLException */ public Integer queryForInt(String sql, Object... paramters) throws SQLException { Integer result = null; try { this.getConnection(); pstmt = connection.prepareStatement(sql); for (int i = 0; i < paramters.length; i++) { pstmt.setObject(i + 1, paramters[i]); } resultSet = pstmt.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); while(resultSet.next()){ String cols_name = metaData.getColumnName(0+1); Object cols_value = resultSet.getObject(cols_name); result = Integer.valueOf(cols_value.toString()); } return result; } catch (SQLException e) { throw new SQLException(e); } finally { releaseConn(); } } /** * 釋放資料庫連線 */ public void releaseConn(){ DBUtils.close(resultSet, pstmt, connection); } }
測試類TestMySQLConnection
package hn.veryedu.jdbc.mysql; import hn.veryedu.jdbc.common.db.DBUtils; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; public class TestMySQLConnection { private static Integer counter = 0; public static void main(String[] args){ for (int i = 1; i <= 2000; i++) { new Thread(new Runnable() { public void run() { Connection conn = null; PreparedStatement pstmt= null; ResultSet resultSet= null; try { conn = DBUtils.getConnection(); synchronized (counter) { System.out.print(Thread.currentThread().getName()); System.out.print(" counter = " + counter++ + " conn = " + conn); System.out.println(); pstmt = conn.prepareStatement("select * from user_t"); resultSet = pstmt.executeQuery(); ResultSetMetaData metaData = resultSet.getMetaData(); int cols_len = metaData.getColumnCount(); while(resultSet.next()){ for(int i=0; i<cols_len; i++){ String cols_name = metaData.getColumnName(i+1); Object cols_value = resultSet.getObject(cols_name); //System.out.println(cols_name+"---"+cols_value); } } DBUtils.close(resultSet, pstmt, conn); //conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } }).start(); } } }
測試類TestJdcb
package hn.veryedu.jdbc.mysql; import java.sql.SQLException; import java.util.List; import java.util.Map; import hn.veryedu.jdbc.common.db.JdbcUtils; public class TestJdcb { /** * @param args * @throws SQLException */ public static void main(String[] args) <