最近仿mybatis写了一个自己的orm框架 项目已上传到github上 https://github.com/skybluehhx/MYORM.git,既然是orm框架肯定需要事务管理器和数据库连接池,下面将介绍我自己实现一个连接池 (主要借助阻塞队列)
首先定义一个接口,给出线程池的基本功能
package Pools; import java.sql.Connection; /** * Created by zoujianglin * 2018/8/25 0025. */ public interface Pool { //获取连接池中的“连接” PoolConnection getPoolConnection(); boolean relasePoolConnection(PoolConnection connection); //连接池大小 int getPoolSize(); //销毁连接池 boolean destroy(); //释放连接,归还连接,使用该方法并不是销毁连接而是 //重新归回线程池,共其他线程复用 boolean relaseConnection(Connection connection); }
在这里我将连接池中的连接做了一个抽象,主要是方便后续扩展,如标定每个连接所属的事务管理器等等
具体定义如下
package Pools; import java.sql.Connection; import java.util.concurrent.atomic.AtomicLong; /** * Created by zoujianglin * 2018/8/25 0025. * PoolConnection为连接池对连接的抽象 */ public class PoolConnection { //表示连接的唯一标识, private long id = 0; private AtomicLong Iid = new AtomicLong(0); // 维持着数据库连接 private Connection connection; //标志着该连接是否被其他线程使用 // false表示连接不可用,表明连接已经被占用 //true表示该连接未被占用,可以正常使用 private volatile boolean isAvailable; //管理该连接的事务管理器,只有从事务管理器中获取的连接才会被设置 暂未使用 // private TransactionManage transactionManage; //创建时,不带参数默认没有被使用 public PoolConnection(Connection connection) { this(connection, true); } public PoolConnection(Connection connection, boolean isAvailable) { this.connection = connection; this.isAvailable = isAvailable; this.id = Iid.getAndIncrement(); } @Override public String toString() { return "PoolConnection{" + "connection=" + connection + ", isAvailable=" + isAvailable + "connection=" + id + '}'; } public long getId() { return id; } public Connection getConnection() { return connection; } public boolean isAvailable() { return isAvailable; } }
//接着是basePoolt提供了对连接池的基本实现 该类有点复杂,比较重要的方法一般都做了注释,具体实现如下
package Pools; import ORMException.DestoryPoolException; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.sql.Connection; import java.sql.Driver; import java.sql.DriverManager; import java.sql.SQLException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** * Created by zoujianglin * 2018/8/25 0025. * 提供连接池的基本功能 */ @Component public abstract class BasePool implements Pool { private static Logger logger = Logger.getLogger(BasePool.class); //数据源,获取连接数据库的基本信息 @Autowired private DataSource dataSource; //用来持有连接池连接 private final BlockingQueue<PoolConnection> blockingQueue; //数据库持有连接数量,初始值为0; private AtomicInteger poolSize = new AtomicInteger(0); //扩容标志,当为true时,表明连接池正在扩容, //一个线程池我们设定只有一个线程进行扩容,后面想了想多个线程同时扩容也没事,所以注释掉 //private AtomicBoolean isDilatation = new AtomicBoolean(false); //初始化标志 只能被初始化一次 private AtomicBoolean isInitialization; //销毁标志 private AtomicBoolean isDestroy = new AtomicBoolean(false); //默认重试次数,当数据库获取连接失败时,将会重试,默认 //重试次数为连接池规定连接数,当为0时将不会重试, private int tryTimes; //增长的步伐,从连接池中获取连接时,如果没有 //获取到连接而连接数小于最大连接时将以该步长增长连接数 //默认步长为4 private int step; public BasePool(DataSource dataSource, BlockingQueue blockingQueue) { this(dataSource, blockingQueue, dataSource.getMinConnection(), 4); } public BasePool(DataSource dataSource, BlockingQueue blockingQueue, int step) { this(dataSource, blockingQueue, dataSource.getMinConnection(), step); } public BasePool(DataSource dataSource, BlockingQueue<PoolConnection> blockingQueue, int times, int step) { this.dataSource = dataSource; this.blockingQueue = blockingQueue; this.tryTimes = times; this.isInitialization = new AtomicBoolean(false); this.step = step; init(); } /** public PoolConnection getConnection(long timeout, TimeUnit unit) { return null; } **/ /** * 获取连接,获取连接时并不能保证当连接池尺寸小于设定的最大连接数时 * 能立马获取到连接,但能保证最终获取连接 * * @return */ public PoolConnection getPoolConnection() { if (!isDestroy.get()) { try { PoolConnection poolConnection = blockingQueue.poll(); if (poolConnection == null) { //判断线程池大小是否达到最大 if (poolSize.get() >= dataSource.getMaxConnection()) {//达到最大阻塞等待 poolConnection = blockingQueue.take(); } else { //if (isDilatation.compareAndSet(false, true)) { //进行判断,并确保每次只有一个线程进行扩增操作 int currentSize; int afterSize; int newSize; while (true) { currentSize = poolSize.get(); afterSize = currentSize + step; newSize = afterSize > dataSource.getMaxConnection() ? dataSource.getMaxConnection() : afterSize; int addNum = newSize - currentSize; //确保只有一个线程进行扩容线程池 if (poolSize.compareAndSet(currentSize, newSize)) { for (int i = 0; i < addNum; i++) { //这里为提高效率可以返回第一个PoolConnecton,其他连接操作可以交由另一个线程操作 blockingQueue.add(getNewOneConnection(dataSource.getUrl(), dataSource.getUserName(), dataSource.getPassword())); } break; } //isDilatation.set(false);看前面关于isDilatation属性的介绍,后发现多线程扩容也没事 } return blockingQueue.take(); //} else { //有线程在扩容,直接阻塞等待返回 // return blockingQueue.take(); //} } } return poolConnection; } catch (InterruptedException e) { e.printStackTrace(); throw new RuntimeException(e); } } throw new DestoryPoolException("连接池已被毁坏"); } public int getPoolSize() { return poolSize.get(); } public int getFreeConnectioNums() { return blockingQueue.size(); } /** * * 归还连接, * * @param connection * @return */ public boolean relasePoolConnection(PoolConnection connection) { blockingQueue.add(connection); return true; } //释放连接,正如你所料的 我们依靠poolSize来确保连接池大小 //所以在释放连接失败时,poolSize的值应该减一,为了避免被错误的 //使用,我们增加限制,传入空值时,将会抛出异常,这里并没有 //做强制的保证,如果用户归还的连接不是连接池中的连接 我们也会 //确保它归还成功,错误的使用该方法该会造成连接池实际数量 //大于现有数量,其实可以使用一个ThrealLocal 保存一个标志 //当线程获取连接时,将标志设置为true,只有带有标志的线程才能归还 //并且归还后需要重新置为false,考虑到该线程池,是内置使用, //这里并没有实现该功能,如果需求特殊,后续考虑补加 public boolean relaseConnection(Connection connection) { if (connection == null) { throw new RuntimeException("请确保释放的连接不为空"); } //在归还前 确保归还的连接可用, boolean falg = false; //poolSize 减一是否成功的标志 try { if (connection.isClosed()) { //连接已关闭,但连接池大小小于规定最小数目 //连接释放失败,直接尺寸减一 poolSize.getAndDecrement(); falg = true; return false; } } catch (SQLException e) { e.printStackTrace(); } finally { if (!falg) { poolSize.getAndDecrement(); } } return relasePoolConnection(new PoolConnection(connection)); } public boolean destroy() { return false; } public void init() { //没有被初始化才进行初始化 if (isInitialization.compareAndSet(false, true)) { logger.error("开始初始化线程池"); try { Class driver = Class.forName(dataSource.getDriverClassName()); DriverManager.registerDriver((Driver) driver.newInstance()); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException(e + "数据库驱动类错误"); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e + "注册数据库驱动失败"); } catch (IllegalAccessException e) { e.printStackTrace(); throw new RuntimeException(e + "注册数据库驱动失败"); } catch (InstantiationException e) { e.printStackTrace(); throw new RuntimeException(e + "注册数据库驱动失败"); } String url = dataSource.getUrl(); String userName = dataSource.getUserName(); String password = dataSource.getPassword(); for (int i = 0; i < dataSource.getMinConnection(); i++) { try { Connection connection = DriverManager.getConnection(url, userName, password); blockingQueue.add(new PoolConnection(connection)); poolSize.getAndIncrement(); //将连接放入阻塞队列 } catch (SQLException e) { logger.error("获取一条数据库连接失败", e); //补入重试机制,确保数据库连接能够完成 while (tryTimes > 0) { try { Connection connection = DriverManager.getConnection(url, userName, password); blockingQueue.add(new PoolConnection(connection)); poolSize.getAndIncrement(); } catch (SQLException e1) { e1.printStackTrace(); logger.error("重试时获取一条数据库连接失败", e); } tryTimes--; logger.error("重试次数剩余" + tryTimes, e); } } } } else { logger.warn("线程池正在被或已经被初始化,一个线程池只能被初始化一次"); throw new RuntimeException("请确保线程池只被初始化一次"); } } private PoolConnection getNewOneConnection(String url, String userName, String password) { try { Connection connection = DriverManager.getConnection(url, userName, password); return new PoolConnection(connection); } catch (SQLException e) { e.printStackTrace(); while (tryTimes > 0) { //重试 try { Connection connection = DriverManager.getConnection(url, userName, password); return new PoolConnection(connection); } catch (SQLException e1) { e1.printStackTrace(); logger.error("重试时获取一条数据库连接失败", e); } tryTimes--; logger.error("重试次数剩余" + tryTimes, e); } throw new RuntimeException("重试次数用光,获取连接失败"); } } }
其中该类中涉及到的数据源结构如下
package Pools; /** * Created by zoujianglin * 2018/8/25 0025. * <p> * 数据源 保留着数据库连接的基本配置 */ public class DataSource { private String driverClassName; private String userName; private String password; private String url; private int maxConnection = 10; private int minConnection = 20; private int timeout; public DataSource(){ } protected DataSource(String driverClassName, String userName, String password, String url) { this.driverClassName = driverClassName; this.userName = userName; this.password = password; this.url = url; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public void setUserName(String userName) { this.userName = userName; } public void setPassword(String password) { this.password = password; } public void setUrl(String url) { this.url = url; } public String getDriverClassName() { return driverClassName; } public String getUserName() { return userName; } public String getPassword() { return password; } public String getUrl() { return url; } public int getMaxConnection() { return maxConnection; } public void setMaxConnection(int maxConnection) { this.maxConnection = maxConnection; } public int getMinConnection() { return minConnection; } public void setMinConnection(int minConnection) { this.minConnection = minConnection; } public int getTimeout() { return timeout; } public void setTimeout(int timeout) { this.timeout = timeout; } }
最后给出数据库连接池的具体实现,默认连接池采用无界阻塞队列
package Pools; import org.springframework.stereotype.Component; import java.util.concurrent.LinkedBlockingQueue; /** * Created by zoujianglin * 2018/8/25 0025. */ public class DefaultPool extends BasePool { public DefaultPool(DataSource dataSource) { super(dataSource, new LinkedBlockingQueue()); } public DefaultPool(DataSource dataSource, int times) { super(dataSource, new LinkedBlockingQueue(), times); } }