实现一个简单的数据库连接池
前言:
最近在搞一个项目,就是要把SqlServer数据全部导入到Oracle中,也要让项目由原来的SqlServer支持Oracle,于是自已在网上找了很多工具,发现导的时候都有问题,而且数据量非常庞大。一开始是自已手动导,将SqlServer数据库导成支持Oracle的sql文件,然后再把这个sql文件导入到Oracle中,发现将一个10万条的sql文件导入到Oracle中都要半小时,简直崩溃了! 想想单个导sql文件的方式属于单线程模式,因为使用PLSQL工具导是只有一个连接,于是就想到了使用多线程的方式导,也就是采用多个连接,多个子任务去导。因此便使用到了资源池的这种模式。使用多线程、资源池方式之后,速度提升了上千倍。
实现思路(如下图所示):
说明:
使用一个池也可实现资源池(即Map<Connetion, Params> pool 这种方式)但是这种逻辑复杂一点,即一个pool中要保证不连接数不能超过最大值又要判断哪些连接已经被占用。而我这里采用两个池来实现,一个是Used池,用来存放正在连接的资源;一个是Pool池,用来存放已经释放连接的资源;这样逻辑清晰简单。
实现步骤:
下面就是数据库连接池的简单实现方式:
1.编写一个对象池的抽象类
package com.core.jdbc; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; public abstract class ObjectPool<T> { /** * 创建对象 */ protected abstract T create(); /** * 验证对象有效性 */ public abstract boolean validate(T t, long createTime); /** * 使对象失效 */ public abstract void expire(T t); private ConcurrentHashMap<T, Long> used; // 正在使用的,保存新建的资源 private ConcurrentHashMap<T, Long> pool; // 未被使用的,保存释放的、未失效的资源,池子里面保存有效可用的对象 private static int MAX_CONNECT_SIZE = 100; // 最大连接数(这里也是pool的最大size,虽然没有定义pool的最大size,但是从整个逻辑上讲,pool的size是小于这个值的) public static int MAX_KEEP_ALIVE_TIME = 3000; // 最大生存时间,这个时间也决定了创建连接的频率 /** * 获得资源 */ public synchronized T getResource() { T t = null; // 初始化 if (null == pool) { pool = new ConcurrentHashMap<T, Long>(); } if (null == used) { used = new ConcurrentHashMap<T, Long>(); } // 超过最大连接数,等待(注意:这里可以加一个拒绝策略,即超时拒绝连接还是继续等待) while (used.size() >= MAX_CONNECT_SIZE) { try { this.wait(50); } catch (InterruptedException e) { e.printStackTrace(); } } // 默认先从池子里面去取 if (pool.size() > 0) { for (Entry<T, Long> entry : pool.entrySet()) { t = entry.getKey(); if (null!=t) { break; } } pool.remove(t); } // 在池子中未取到,创建一个新的 if (null == t) { t = create(); } used.put(t, System.currentTimeMillis()); this.notifyAll(); return t; } /** * 释放某个资源 */ public synchronized void release(T t) { if (null==t) { return; } while (used.size()==0) { try { this.wait(50); } catch (InterruptedException e) { e.printStackTrace(); } } // 判断是否过有效期 if (validate(t, used.get(t))) { // 放入池中 pool.put(t, System.currentTimeMillis()); used.remove(t); } else { expire(t); used.remove(t); } this.notifyAll(); } }
2.编写数据库连接池的具体实现类
public class ConnectionPool extends ObjectPool<Connection> { private static int count = 0; public ConnectionPool() { try { Class.forName("oracle.jdbc.driver.OracleDriver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } @Override protected Connection create() { Connection conn = null; try { conn = DriverManager.getConnection("jdbc:oracle:thin:@//127.0.0.1:1521/SZNY", "caoxiaobo", "123456"); } catch (SQLException e) { e.printStackTrace(); } count ++; logger.debug("建立连接次数" +count); return conn; } @Override public boolean validate(Connection o, long createTime) { if (System.currentTimeMillis() - createTime > MAX_KEEP_ALIVE_TIME) return false; return true; } @Override public void expire(Connection o) { try { o.close(); } catch (SQLException e) { e.printStackTrace(); } } }
3.编写JDBC连接池单例类来确保只有一个池(确保ConnectionPool 对象唯一,即程序中所有的连接都从一个pool中去取)
public class JdbcConnection { private JdbcConnection () { } private static class Singleton { private static ConnectionPool pool = new ConnectionPool(); } public static Connection getConnection() { return Singleton.pool.getResource(); } public static void release(Connection conn) { Singleton.pool.release(conn); } }
4.并发测试类:
public class PoolTest { public static void main(String[] args) { Runnable run = new Runnable() { @Override public void run() { Connection conn = JdbcConnection.getConnection(); try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } JdbcConnection.release(conn); } };
// 创建2000个线程,模拟并发 for (int i=0; i<2000; i++) { Thread thread = new Thread(run); thread.start(); } } }
测试结果:
假设并发请求有2000个(假设数据库最大连接数为150,这里设置要比它正常值小一点,即100),如果不使用资源池,那么就需要不断的创建、销毁2000次连接,对于服务器的性能来说影响还是比较大的。通过这个示例,我们可以看到这个结果(创建、销毁)远远小于2000次,大概测试了一下平均100-120之间。当然这里的这个值是根据它设定的生存时间来决定的。