用apache.commons.pool 实现Access数据库连接池

博客开张,把自己的一些经验做点记录.

朋友的在维护一个老的内部网站系统,数据库使用access,该系统访问量一多经常会报sql错误,提示客户端过多问题.

查看了系统代码,发现这个系统的数据库连接代码每次都是重新创建的.

public static Connection getConn() throws ClassNotFoundException,
            SQLException {
        Connection conn = null;
        String driver = "jdbc:odbc:driver={microsoft access driver (*.mdb)};dbq=d:\\data.mdb";
               Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");   
        conn = DriverManager.getConnection(driver);
        return conn;
    }

 这样的问题就很严重,首先就是无法控制并发的数据库操作,导致并发高于一定量后access数据库直接报错提示客户端过多。

其次就是数据库操作效率低下,每次建立连接和释放连接的效率很低。

解决这个问题需要对数据库连接上加入连接池,保证数据库连接安全可控,高效。

由于是老系统要求改动的代码尽量少,google了一把也没找到可以直接使用access文件作为数据库连接池的代码,就自己动手写了一个。

该连接池只要依赖apache.commons.pool 开源组件。

首先实现一个数据库连接创建,销毁的工厂类DBConnectFactory该类实现了接口org.apache.commons.pool.PoolableObjectFactory

package base;

import java.sql.Connection;
import java.sql.DriverManager;

import org.apache.commons.pool.PoolableObjectFactory;

/**
 * 数据库连接工厂类
 *
 * @author binda.mabd
 * @version 1.0
 * @date 2009-4-3
 */
public class DBConnectFactory implements PoolableObjectFactory {
	private int count =0 ;
	private String driver ;
	private String className;
	public DBConnectFactory(String className,String driver){
		this.className = className;
		this.driver = driver;
	}
	/**
	 * 对象初始化后加入池的时候使用,设置对象为可用状态。
	 */
	public void activateObject(Object arg0) throws Exception {
	//直接加入
		
	}
	/**
	 * 对象销毁方法
	 */
	public void destroyObject(Object arg0) throws Exception {
		 System.err.println("destroyObject Object " + arg0.getClass().getName());
		if(arg0!=null){
			Connection con = (Connection)arg0;
			try{
			con.close();
			 count --;
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
	/**
	 * 对象创建方法
	 */
	public Object makeObject() throws Exception {
		try {
			Class.forName(className);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}  
		  count ++;
		   return  DriverManager.getConnection(driver);

	}
	/**
	 * 非创建对象回到池的时使用,设置对象为可用.
	 */
	public void passivateObject(Object arg0) throws Exception {
		Connection con = (Connection)arg0;
		//如果非自动commit类型连接,强制comit后返回连接池
		if(!con.getAutoCommit()){
			con.commit();
		}

	}
	/**
	 * 状态检查对象是否可用
	 */
	public boolean validateObject(Object arg0) {
		
		try{
		Connection con = (Connection)arg0;
		return con!=null&&!con.isClosed();
		}catch(Exception e){
		return false ;
		}
				
	}

}

有了DBConnectFactory工厂类,就可以通过org.apache.commons.pool.impl.GenericObijectPool 来创建一个自定义的连接池.

package base;

import java.sql.Connection;

import org.apache.commons.pool.PoolableObjectFactory;
import org.apache.commons.pool.impl.GenericObjectPool;

public class SimpleAccessPool {
	
    GenericObjectPool pool  = null; 
    PoolableObjectFactory factory = null;
    public SimpleAccessPool (String accessFilePath){
		  String className = "sun.jdbc.odbc.JdbcOdbcDriver"	;
		  String driver = "jdbc:odbc:driver={microsoft access driver (*.mdb)};dbq=";
		  driver = driver + accessFilePath;
    	factory = new DBConnectFactory(className,driver);
    	
    }
    public void init(){
        pool = new GenericObjectPool(factory);
        //最大连接数
         pool.setMaxActive(30); 
         //最大空闲连接数
         pool.setMaxIdle(20);
              //连接等待1分钟
         pool.setMaxWait(60000);
    }
/** 获得连接*/
     public Connection getConnection() {
    	try {
			return	(Connection) pool.borrowObject();
			
		}catch (Exception e){
			 e.printStackTrace();
				throw new RuntimeException("get connect error",e);
		}
    	
     }
     /**释放连接*/
     public void relaseConnection(Connection con){
         try {
			pool.returnObject(con);
		} catch (Exception e) {
			  e.printStackTrace();
			throw new RuntimeException("relase connect error",e);
		}
     }
	
	}

 写了一个测试类Main,并发1000个数据库查询请求

package base;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;




public class DBTest {
	
	static SimpleAccessPool connPoll = null ;

	public static void main(String[] args) {
		 String  current_mdb_path = "D:\\data.mdb";
		 connPoll = new SimpleAccessPool(current_mdb_path);
		 connPoll.init();
		 for(int i=0;i<1000;i++){
			 Thread run = new Thread(new TestRun(i+""));
			 run.start();
		 }
	}

}

/** 
 *并发测试考虑实现runable
 * */
class TestRun implements Runnable{
	   private String id ;
	 
	   public TestRun(String id){
		   this.id = id;
	   }
		public void run() {
		  Connection conn =DBTest.connPoll.getConnection();
		  long t = System.currentTimeMillis();
	   	  PreparedStatement pstmt = null;
	      ResultSet rs  = null ;
	         String sql_delele = "select * from news where news_id <?";
	         try {
	             pstmt = conn.prepareStatement(sql_delele);
	             pstmt.setInt(1,100);
	             rs = pstmt.executeQuery();
	         } catch (SQLException ex) {
	             ex.printStackTrace();
	         }
	         try{
	        	 rs.close();
	        	 pstmt.close();
	         }catch (SQLException ex) {
	             ex.printStackTrace();
	         }
	         DBTest.connPoll.relaseConnection(conn);
	     	System.out.println("ok!cost time ms:"+(System.currentTimeMillis()-t));
		}
		
	}

好了,一个简单的数据库连接池ok了.

遇到的疑问:

数据库连接Connection重复使用是否会有什么状态上的问题, 在回收的池的时候有什么方法把连接的状态初始化.我代码中做了一个检查,回收Connection时如果非自动commit连接则强制commit 避免重复使用connection事务回滚错误.

开源的连接池框架没有研究过,是否能支持直接读取Access数据库文件方式获取数据源呢.

相关推荐