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) throws SQLException { // TODO Auto-generated method stub for (int i = 1; i <= 20; i++){ JdbcUtils jdbc = new JdbcUtils(); String sql = "select * from user_t"; List<Map<String, Object>> list = jdbc.findModeResult(sql, null); for (int j = 0; j < list.size(); j++) { System.out.println(list.get(j)); } } } }
通过这两个测试进行数据库访问的测试,发现无论运行多少次,数据库当前的连接数都是不变的,这样就再也不用担心数据库的连接数会满啦!
项目源码地址:https://github.com/jiafuweiJava/jdbc_c3p0