13
回答
c3p0没有正常回收链接
利用AWS快速构建适用于生产的无服务器应用程序,免费试用12个月>>>   

各位好,最近在使用c3p0做数据库连接池,不过在使用的过程中,一直出现连接不能正常回收的情况,访问量大了,网站就没法访问了,可能是个人理解的不够清晰,或者编程习惯误区,一直没有找到原因,还请各位给指点一下。

环境:window server 2000 + tomcat + oracle

分析截图(通过jconsole分析):

jconsole分析截图

代码如下所示:

1.这是调用通过c3p0连接数据库的部分代码(在网上找的)

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.SQLException;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.menkj.init.DBParameter;
import com.menkj.tools.Out;

public class ConnectionFactory {
    private static String classname="oracle.jdbc.driver.OracleDriver";
    private static String dburl="jdbc:oracle:thin:@127.0.0.1:1521";
    private static String dbname = "orcl";
    private static String dbuser ="sdt";
    private static String dbpassword ="ma";
    
    private ConnectionFactory(){
        	
    }    

    private static ComboPooledDataSource ds = null;

    static {
    	dburl = DBParameter.getDburl();
    	
    	dbname = DBParameter.getDbname();
    	dbuser = DBParameter.getDbuser();
    	dbpassword = DBParameter.getDbpassword(); 
    	initDS();
    }
  
    private  static void initDS(){
    	try {
            // Logger log = Logger.getLogger("com.mchange"); // 日志
              // log.setLevel(Level.WARNING);
              ds = new ComboPooledDataSource();
            // 设置JDBC的Driver类
              ds.setDriverClass(classname);  // 参数由 Config 类根据配置文件读取
              // 设置JDBC的URL
              
              ds.setJdbcUrl(dburl+":"+dbname);
            // 设置数据库的登录用户名
              ds.setUser(dbuser);
            // 设置数据库的登录用户密码
              ds.setPassword(dbpassword);
            // 设置连接池的最大连接数
              ds.setMaxPoolSize(80);
            // 设置连接池的最小连接数
              ds.setMinPoolSize(1);
            // 设置两次连接的间隔数
              ds.setAcquireRetryDelay(1000);
             //最大空闲时间,180秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0
              ds.setMaxIdleTime(180); //s
              
             //检查连接池中所有的空闲连接时间, 单位秒,设置为3分钟
              ds.setIdleConnectionTestPeriod(480);
              
              //取得连接的同时将校验连接的有效性,因为默认为false,建议设置成true
              ds.setTestConnectionOnCheckin(true);
              
              //当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出SQLException,如设为0则无限期等待,单位毫秒。因为默认为0,建议设置成60秒
              ds.setCheckoutTimeout(60000);
            //<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 -->
              ds.setAcquireIncrement(1);
              
              ds.setDebugUnreturnedConnectionStackTraces(true);
              
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
    }
    
    public static synchronized Connection getConnection() {
        Connection con = null;
        
        try {	
            con = ds.getConnection();
            
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        return con;
    }
    public static void closeConnection() {
    	if(ds!=null){
    		ds.close();
    	}
    }
}

2、下面是一个访问数据库的通用类:

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.menkj.tools.Out;


public class DBConn {

    private Connection con;
    private Statement stm;
    PreparedStatement pstmt = null;
    private ResultSet rs;
    private Log log = LogFactory.getLog(this.getClass()); 
    public DBConn(){
    }
    
    /**
     * 获取数据库连接
     *
     * @return 数据库连接
     */
    public Connection getCon(){   
    	
    	try {
			if(con == null || con.isClosed()){
				con = ConnectionFactory.getConnection();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}
    	return con;
    	
    }
    
    /**
     * 创建stmt
     * @return
     */
    public Statement getStatement(){
  	  try{
  		  if(con==null || con.isClosed()){
  			  getCon();
  		  }
  	     stm=con.createStatement();
  	  }catch(Exception e){
  		  e.printStackTrace(System.err);
  		 log.error("获取statement失败!"+e.toString());
  		 closed();
  	  }
  	   return stm;
  	}
    
    /**
     * 获取能够滚动的statement
     * @return
     */
  	public Statement getStatemented(){
  	  
  	 try{
  		if(con==null || con.isClosed()){
  			getCon();
  		}
  	    stm=con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
  	  }catch(Exception e){
  		  e.printStackTrace(System.err);
  		  log.error("获取statement失败!"+e.toString());
  		  closed();
  	  }
  	  
  	  return stm;
  	  
    }
  	
  	/**
  	 * 关闭连接
  	 */
   	public void closed(){
   		try{
    		if(rs!=null){
    			rs.close();
    			rs = null;
    		}
    	}catch(Exception e){
    		e.printStackTrace();
    	}
    	
   		try{
    		if(stm!=null){
    			stm.close();
    			stm = null;
    		}
    	}catch(Exception e){
    		e.printStackTrace();
    	}    	
   		
    	try{
    		if(con!=null)
    		{
    			con.close();
    			//con = null;
    		}
    	}catch(Exception e){
    		e.printStackTrace();
    	}    	
    }
   	
  	/**
  	 * 更新 添加 删除数据库
  	 * @param sql
  	 * @return
  	 *  -1: 连接数据库失败
  	 *  -2:  更新数据出现异常
  	 *  >0:  数据更新成功
  	 */
	public int executeUpdate(String sql){
		int res=-1;

	    try {
			stm = getStatement();//创建Statement
			res = stm.executeUpdate(sql);//执行更新
	    } catch (SQLException e) {
	    	try {
	    		if(con!=null && !con.isClosed()){
	    			con.rollback();
					con.setAutoCommit(true);
	    		}
			} catch (SQLException e1) {
				e1.printStackTrace();
				res = -2;
			}
			log.error("更新数据库失败!"+e.toString());
			
			e.printStackTrace();
			res = -2;
			
			throw new RuntimeException("更新数据库失败:"+e.getMessage());
	    }finally{
	    	closed();//关闭数据库
	    }
	    return res;
	}
	
	/**
	 * 更新数据,并返回被更新的数据的id
	 * @param sql  执行语句
	 * 		  sequence 该表对应的序列名称
	 * @return
	 */
	public int executeUpdate_id(String sql,String sequece){
		int id = -1;		
		try {
			stm = getStatement();//创建Statement
			id = stm.executeUpdate(sql);
			
			//String getId = "select @@IDENTITY as id";
			//获取添加的数据id
			String getId = "select "+sequece+".currval as id from dual";//获取在此会话中的添加的记录的id
			ResultSet rs = stm.executeQuery(getId);
			if(rs.next()){
				id= rs.getInt("id");
			}
			
		} catch (SQLException e) {
			e.printStackTrace();
			log.error("更新数据库失败!"+e.toString());
			throw new RuntimeException("更新数据库失败:"+e.getMessage());
		}finally{
			closed();
		}
		return id;
	}
	
	/**
	 * 查询某条记录是否存在
	 * @param sql
	 * @return
	 * 
	 * 	 1 :不存在该记录
	 * 	-1:连接数据库失败
	 *  -2:该内容已存在
	 *  -3:查询数据出现异常
	 */
	public byte queryExists(String sql){
		byte res = -1;
		try {
			stm = getStatement();
			rs = stm.executeQuery(sql);
			if(rs.next()){
				res = -2;//查询到所要的数据
			}
			else {
				res =  1;
			}
		} catch (SQLException e) {
			
			log.error("查询数据失败!"+sql);
			e.printStackTrace();
			res = -3;
		}finally{
			closed();
		}
		
		return res;//数据库操作错误
	}	
	
	/**
	 * 查找记录并返回结果集对象
	 * return rs 
	 * 		  失败是返回null
	 */
	public ResultSet query(String sql){
		rs = null;
        if(sql==null)sql="";
     	try{
     	    stm=getStatement();
     	    rs=stm.executeQuery(sql);
    	}
     	catch(Exception e){
     		e.printStackTrace();
     		log.error("查询记录失败!"+e+sql);
     		closed();
     	}
        return rs; 
        
  	}
	

	/**
	 * 查询所有记录
	 * return rs 
	 * 失败是返回null
	 */
	public ResultSet getAllResults(String sql){
		
		try{
			stm=getStatemented();
			rs=stm.executeQuery(sql);
		}
		catch(SQLException e){
			e.printStackTrace();
			log.error("查询记录失败!"+e+sql);
			closed();
		}
		return rs;
		
	}
	
	/**
	 * 获取记录条数
	 * @param sql
	 * @return
	 */
	public int getRecordcount(String sql){
		int recordcount=0;
		
		try{
			stm=getStatemented();
			rs=stm.executeQuery(sql);
			rs.last();
			recordcount=rs.getRow();
		}
		catch(SQLException e){
			e.printStackTrace();
			log.error("查询记录失败!"+e+sql);
		}finally{
			closed();
		}
		return recordcount;
		
	}
	
	/**
	 * 执行更新或删除
	 * 
	 * @param sql
	 * @param param
	 *            返回: -1:数据库连接失败 -2:更新失败 -3:执行出现异常 1:更新成功
	 */
	public int execPrepared(String sql, Object[] param) {
		int res = -1;
		try {
			 getCon();
			 if(con==null){
				 return res;
			 }
			 pstmt = con.prepareStatement(sql);
			if (param != null) {
				for (int i = 0; i < param.length; ++i) {
					log.info(param[i]);
					if (param[i] instanceof String) {
						pstmt.setString(i + 1, (String) param[i]);
					} else if (param[i] instanceof Integer) {
						pstmt.setInt(i + 1, ((Integer) param[i]).intValue());
					} else if (param[i] instanceof Long) {
						pstmt.setLong(i + 1, ((Long) param[i]).intValue());
					} else if (param[i] instanceof Timestamp) {
						pstmt.setTimestamp(i + 1, (Timestamp) param[i]);
					} else {
						if(param[i]==null||param[i].equals("")){
							param[i] = "";
						}
						pstmt.setObject(i + 1, param[i]);
					}

				}

			}

			pstmt.execute();
			res = 1;
			log.debug("提交SQL事务:" + sql);
			con.commit();
			
		} catch (Exception e) {
			res = -3;
			log.error("execute sql error:" + sql, e);
			try {
				con.rollback();
			} catch (SQLException e1) {
				log.error("execute rollback:", e);
			}
			 try {
				con.setAutoCommit(true);
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		} finally {
			try {
				if ((pstmt != null)) {
					pstmt.close();
					pstmt = null;
				}
			} catch (SQLException e) {
				log.error("execute:", e);
			}
			try {
				if(con != null)
					con.close();
			} catch (SQLException e) {
				log.error("execute:", e);
			}
		}

		return res;
	}
	
	
	/**
	 * 批量执行sql语句
	 * @param  sql sql语句数组
  	 * @return 
  	 * 		  1- 执行成功
  	 *        -1:连接数据库失败
  	 *        -2:操作有误
  	 *        -3:数据执行异常
	 */
	public byte BatchUpdate(List sqls) {
		byte res  =-2;

	    if(sqls.size()==0){
	    	return -2;
	    }	    	
		try {
			
			stm=getStatemented();
			if( con ==null){//获取数据库连接
				return -1; //数据库连接失败
			}
			con.setAutoCommit(false);
			for(int i=0;i<sqls.size();i++ ){
				String sql = (String)sqls.get(i);
				if(sql!=null && sql.compareTo("")!= 0)
					stm.addBatch(sqls.get(i).toString());
			}
			
			stm.executeBatch();//执行批处理
			con.commit();//提交事务
			res =1;
			con.setAutoCommit(true);
		} catch (SQLException e) {
			try {
				con.rollback();
				con.setAutoCommit(true);
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
			System.out.println("SQLException: " + e.getMessage());
			System.out.println("SQLState: " + e.getSQLState());
			System.out.println("Message: " + e.getMessage());
			System.out.println("Vendor error code: " + e.getErrorCode());
			System.out.print("Update counts: ");
			
			res = -3;
			e.printStackTrace();
		}//设置手动提交事务
		finally{
			try {
				con.setAutoCommit(true);
			} catch (SQLException e) {
				e.printStackTrace();
			}
			closed();
		}
		
		return res;
	}
		
}

3、调用的方法如下:

一个是更新执行的方式,另一个是查找数据库的方式。

public class ChannelClassManager   extends UtilMethod  implements OrdinaryCon{
	private DBConn db = new DBConn();
	private Log logger = LogFactory.getLog(this.getClass());
	/**
	 *  
	 * 提交成功返回提交后的id
	 * 不成功返回错误的状态
	 * 
	 */
	public int addInfo(Object o) {
		ChannelClassInfo cci = new ChannelClassInfo();
		cci = (ChannelClassInfo)o;
		
		String sql = "insert into 语句";
		
		return db.executeUpdate(sql);
	}



	/**
	 * 根据条件获取相关信息 
	 */
	public Object getInfoByStr() {
		db = new DBConn();
		ChannelClassInfo  cci = new ChannelClassInfo(); 
		ResultSet rs = null;		
		StringBuffer sql = new StringBuffer();
		sql.append("select * from 表 where ID!=10 ");
		
		try {
			rs = db.getAllResults(sql.toString());
			if(rs!=null&&rs.next()){
				cci = (ChannelClassInfo)this.getRecordInfo(rs);
			}
		} catch (SQLException e) {
			logger.error("频道查询信息失败,执行的sql语句为:"+sql.toString()+"",e);
			e.printStackTrace();
		}finally{
			db.closed();
	}
		return cci;
	}
}

请各位给看看怎么回事啊?

举报
sdlsc
发帖于6年前 13回/3K+阅
共有13个答案 最后回答: 6年前
代码问题挺多,释放连接的方法不统一,例如 getAllResults 这个方法,在异常发生里关闭连接,可是正常执行又不关闭

引用来自“红薯”的答案

代码问题挺多,释放连接的方法不统一,例如 getAllResults 这个方法,在异常发生里关闭连接,可是正常执行又不关闭

多谢大哥指点,向您说的情况,我用下面这种方式在finally里面不能释放连接吗?

try {  

             rs = db.getAllResults(sql.toString());  

             if(rs!=null&&rs.next()){  

                 cci = (ChannelClassInfo)this.getRecordInfo(rs);  

             }  

         } catch (SQLException e) {  

             logger.error("频道查询信息失败,执行的sql语句为:"+sql.toString()+"",e);  

             e.printStackTrace();  

         }finally{  

             db.closed();  

     }  
所有连接应该统一在 final 里释放,另外如果是 web 项目可考虑统一在 Filter 里进行释放,以避免一些因为编码疏忽而导致的连接泄漏问题。

getAllResults 方法不能在finally中释放的,要不然我就取不到resultset了,只能是异常的时候再catch里面直接关闭,如果正常执行,则在调用这个方法的地方再finally里面关闭。这样应该没有问题吧?

大哥,您说的通过filter里面释放,不过我一次请求可能会建多个对象,每个对象可能会用到一个数据库链接,这样我怎么能找到哪些应该释放啊?

引用来自“红薯”的答案

所有连接应该统一在 final 里释放,另外如果是 web 项目可考虑统一在 Filter 里进行释放,以避免一些因为编码疏忽而导致的连接泄漏问题。

getAllResults 方法不能在finally中释放的,要不然我就取不到resultset了,只能是异常的时候再catch里面直接关闭,如果正常执行,则在调用这个方法的地方再finally里面关闭。这样应该没有问题吧?

大哥,您说的通过filter里面释放,不过我一次请求可能会建多个对象,每个对象可能会对应新建一个数据库链接,这样我怎么能找到哪些应该释放啊?

--- 共有 1 条评论 ---
红薯统一获取connection的方法,获取到连接后放到 ThreadLocal 中,这样一个请求多次获取的都是同一个连接实例 6年前 回复

引用来自“晓峰路”的答案

引用来自“红薯”的答案

所有连接应该统一在 final 里释放,另外如果是 web 项目可考虑统一在 Filter 里进行释放,以避免一些因为编码疏忽而导致的连接泄漏问题。

getAllResults 方法不能在finally中释放的,要不然我就取不到resultset了,只能是异常的时候再catch里面直接关闭,如果正常执行,则在调用这个方法的地方再finally里面关闭。这样应该没有问题吧?

大哥,您说的通过filter里面释放,不过我一次请求可能会建多个对象,每个对象可能会对应新建一个数据库链接,这样我怎么能找到哪些应该释放啊?

恩,确实是可行

呵呵,我还是想弄明白我这段代码到底哪个地方有漏洞,没有正常释放链接呢?

按照这里的番薯说的将链接取出丢到  ThreadLocal 中 就可以了。如果还不会你去看看 hibernate 的sessionFactory 的代码就知道了道理一样的!

引用来自“fens111”的答案

按照这里的番薯说的将链接取出丢到  ThreadLocal 中 就可以了。如果还不会你去看看 hibernate 的sessionFactory 的代码就知道了道理一样的!

恩,这个方法确实没有问题,我主要想弄明白,我写的这个问题出在哪里,以免以后会犯同样的错误?

executeUpdate_id(String sql,String sequece)
里面有resultset,close了?此rs非你全局定义的rs!
getAllResults可以返回个被全局的rs指向的记录集,但是,如果你多获取几次,好玩了,前面几个就会泄漏了。
全局指针只有1个,你确认没丢过前面的记录集?其它的也是类似。
你这种写法,很危险。就算连接池正常回收了这个连接,你的记录集没关闭,然后开新的,原先没关闭的记录集之类还在那里,然后开启的游标过多,结果到最后,你就会取不到任何东西,然后,你会认为是连接没被正常释放。
自己检查下还有这种临时的东西没,你那全局的根本就接管不了它们的关闭啊。
你都没关闭,那不出问题就见鬼了。而根据错误信息,你看看。不是说链接没释放,而是你的记录集就没去关闭,前面开启又没关闭的记录集过多,导致阻塞。
你现在去处理那连接回收问题,不管你处理不处理,短期是看不出问题的。就算你处理了回收问题,我上面说的你不去处理,照样出问题。
个人认为,根据错误信息,根本就不是连接没被回收,而是我上面所说的。

顶部