用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数据库文件方式获取数据源呢.