数据库连接池(Connection pooling)是程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由程序动态地对池中的连接进行申请,使用,释放。

大家可以想一下,如果项目中没有数据库连接池会是一个什么样的结果?每访问一次数据库都会新建一个数据库连接,如果同事有成百上千的请求需要访问数据库那么项目会惨不忍睹。

数据库连接池就是在项目启动是就已经创建了一定数量的数据库连接,有请求访问数据库时,不需要新建数据库连接,直接在连接池中拿就可以,用完了记得放到池子里面就行。这样的话,连接池中的数据库连接会不断的重复利用,大大提高了对数据库操作的性能。

数据库连接池的运行机制有以下几点:

1.程序启动时创建数据库连接池。

2.使用连接时,从连接池中拿可用的数据库连接。

3.使用完,将数据库连接放入数据库中。

那么要想手动代码实现数据库连接池,需要哪些步骤了?下面会贴出源代码以及简单的注释。

首先需要一个数据库连接池的配置文件jdbc.properties,内容如下:


#驱动名称
jdbcDriver=com.mysql.jdbc.Driver
#连接URL
jdbcUrl=jdbc:mysql://localhost:3306/lcma
#数据库账户名
userName=root
#数据库密码
password=iflytek
#初始化连接数
initCount=10
#步进数量
stepSize=4
#最大连接池个数
poolMaxSize=150



既然是数据连接池,那么池子里面放的肯定是很多个数据库连接,一个数据库连接就是一个对象,建一个数据库连接类 PooledConnection.java代表数据库连接对象。类里面有个isBusy属性,用来表示该连接对象是否正在使用,若使用中用true表示,反之为false。代码如下:


package com.lcma.conn;

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

/**
 * <p>Title      : 存储连接池中对象属性</p>
 * <p>Description: [子应用名]_[模块名]</p>
 * <p>Copyright  : Copyright (c) 2016</p>
 * <p>Company    : 科大讯飞</p>
 * @author       : lcma
 * @version      : 1.0
 */
public class PooledConnection {
	
	/**
	 * 连接管道对象
	 */
	private Connection conn;
	
	/**
	 * 连接状态,true-繁忙,false-空闲
	 */
	private boolean isBusy = false;
	
	public PooledConnection(Connection conn, boolean isBusy){
		this.conn = conn;
		this.isBusy = isBusy;
	}
	
	public void close(){
		this.isBusy = false;
	}

	public Connection getConn() {
		return conn;
	}

	public void setConn(Connection conn) {
		this.conn = conn;
	}

	public boolean isBusy() {
		return isBusy;
	}

	public void setBusy(boolean isBusy) {
		this.isBusy = isBusy;
	}

	/**
	 * <p>Discription:创建查询方法,用于测试使用</p>
	 * @param sql
	 * @return
	 * @author       : lcma
	 * @update       : 2016年12月5日下午11:19:18
	 */
	public ResultSet queryBySql(String sql){
		ResultSet rs = null;
		Statement sm = null;
		try {
			sm = conn.createStatement();
			rs = sm.executeQuery(sql);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return rs;
	}
	
	
}



创建连接池接口类,IMyPool.java


package com.lcma.pools;

import com.lcma.conn.PooledConnection;

/**
 * <p>Description: 连接池接口</p>
 * <p>Copyright  : Copyright (c) 2017</p>
 * @author       : lcma
 * @version      : 1.0
 */
public interface IMyPool {
	
	/**
	 * <p>Discription:创建连接</p>
	 * @param count
	 * @author       : lcma
	 * @update       : 2016年12月4日下午1:11:46
	 */
	public void createConnection(int count);
	
	/**
	 * <p>Discription:获取连接</p>
	 * @return
	 * @author       : lcma
	 * @update       : 2016年12月4日下午1:11:34
	 */
	public PooledConnection getConnection();

}



实现连接池接口类,MyPoolImpl.java

1.读取配置文件,创建连接池

2.获取连接,判断是否有空闲的连接并且是有效的连接


package com.lcma.pools.impl;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
import java.util.Vector;

import com.lcma.conn.PooledConnection;
import com.lcma.pools.IMyPool;

/**
 * <p>Title      : 连接池接口实现类</p>
 * <p>Description: [子应用名]_[模块名]</p>
 * <p>Copyright  : Copyright (c) 2016</p>
 * <p>Company    : 科大讯飞</p>
 * @author       : lcma
 * @version      : 1.0
 */
public class MyPoolImpl implements IMyPool {
	/**
	 * 驱动名称
	 */
	private String jdbcDriver;
	/**
	 * 连接地址
	 */
	private String jdbcUrl;
	/**
	 * 用户名
	 */
	private String userName;
	/**
	 * 密码
	 */
	private String password;
	/**
	 * 初始化连接数
	 */
	private int initCount;
	/**
	 * 步进连接数
	 */
	private int stepSize;
	/**
	 * 最大连接数
	 */
	private int poolMaxSize;
	
	/**
	 * 连接池容器
	 */
	private static Vector<PooledConnection> pooledConnections = new Vector<PooledConnection>();
	
	/**
	 * 构造函数,执行初始化方法
	 */
	public MyPoolImpl() {
		init();
	}
	
	/**
	 * <p>Discription:初始化连接池</p>
	 * @author       : lcma
	 * @update       : 2016年12月5日下午8:42:37
	 */
	private void init(){
		InputStream is = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
		Properties pro = new Properties();
		try {
			pro.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		}
		jdbcDriver = pro.getProperty("jdbcDriver");
		jdbcUrl = pro.getProperty("jdbcUrl");
		userName = pro.getProperty("userName");
		password = pro.getProperty("password");
		initCount = Integer.parseInt(pro.getProperty("initCount"));
		stepSize = Integer.parseInt(pro.getProperty("stepSize"));
		poolMaxSize = Integer.parseInt(pro.getProperty("poolMaxSize"));
		
		try {
			Driver driver = (Driver)Class.forName(jdbcDriver).newInstance();
			//将driver注册
			DriverManager.registerDriver(driver);;
		} catch (Exception e) {
			e.printStackTrace();
		}
		//创建连接
		createConnection(initCount);
	}

	/**
	 * <p>Discription:创建连接</p>
	 * @param count
	 * @author       : lcma
	 * @update       : 2016年12月4日下午1:11:46
	 */
	@Override
	public void createConnection(int count) {
		if(poolMaxSize<=0 || pooledConnections.size()+count > poolMaxSize){
			System.out.println("创建连接失败,超过最大连接数");
			throw new RuntimeException("创建连接失败,超过最大连接数");
		}
		try {
			//循环创建连接
			for(int i = 0; i < count; i++){
	        	 //创建连接
				Connection connection = DriverManager.getConnection(jdbcUrl, userName, password);
			    //实例化连接池中的连接
				PooledConnection pooledConnection = new PooledConnection(connection, false);
				//存入连接池容器
				pooledConnections.add(pooledConnection);
			}
         } catch (SQLException e) {
			e.printStackTrace();
		}
		
	}

	/**
	 * <p>Discription:获取连接</p>
	 * @return
	 * @author       : lcma
	 * @update       : 2016年12月4日下午1:11:34
	 */
	@Override
	public PooledConnection getConnection() {
		if(pooledConnections.size()<=0){
			System.out.println("获取连接失败,连接池为空");
			throw new RuntimeException("获取连接失败,连接池为空");
		}
		PooledConnection connection = getRealConnection();
		//判断是否为空
		while(connection == null){
			//创建connection,步进数
			createConnection(stepSize);
			//重新获取连接,有可能获取的还为空,采用while循环判断
			getRealConnection();
			//防止其他线程过来拿连接
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		return connection;
	}
	
	//判断我们是否拿到有效的连接对象
	private synchronized PooledConnection getRealConnection(){
		//先判断连接池是不是有我们需要的空闲连接对象
		for(PooledConnection connection : pooledConnections){
			//未处于繁忙状态
			if(!connection.isBusy()){
				Connection conn = connection.getConn();
				try {
					//判断这个连接是不是有效,isValid就是创建了一个statement,执行sql语句,看是否成功
					if(!conn.isValid(2000)){
						Connection validConn = DriverManager.getConnection(jdbcUrl, userName, password);
						connection.setConn(validConn);
					}
				} catch (SQLException e) {
					e.printStackTrace();
				}
				//设置为繁忙
				connection.setBusy(true);
				return connection;
			}
		}
		return null;
	}

	public String getJdbcDriver() {
		return jdbcDriver;
	}

	public void setJdbcDriver(String jdbcDriver) {
		this.jdbcDriver = jdbcDriver;
	}

	public String getJdbcUrl() {
		return jdbcUrl;
	}

	public void setJdbcUrl(String jdbcUrl) {
		this.jdbcUrl = jdbcUrl;
	}

	public String getUserName() {
		return userName;
	}

	public void setUserName(String userName) {
		this.userName = userName;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	public int getInitCount() {
		return initCount;
	}

	public void setInitCount(int initCount) {
		this.initCount = initCount;
	}

	public int getStepSize() {
		return stepSize;
	}

	public void setStepSize(int stepSize) {
		this.stepSize = stepSize;
	}

	public int getPoolMaxSize() {
		return poolMaxSize;
	}

	public void setPoolMaxSize(int poolMaxSize) {
		this.poolMaxSize = poolMaxSize;
	}


}


创建PoolManager类,利用内部类单例模式解决多线程问题,

多个线程在加载内部类的时候线程是互斥的,解决了线程安全问题。


package com.lcma.manager;

import com.lcma.pools.impl.MyPoolImpl;

/**
 * <p>Title      : 利用内部类单例模式解决多线程问题</p>
 * <p>Description: [子应用名]_[模块名]</p>
 * <p>Copyright  : Copyright (c) 2016</p>
 * <p>Company    : 科大讯飞</p>
 * @author       : lcma
 * @version      : 1.0
 */
public class PoolManager {
	
	private static class creatPool{
		private static MyPoolImpl poolImpl = new MyPoolImpl();
	}
	
	//多个线程在加载内部类的时候线程是互斥的,所以用单例模式的内部类形式避免线程混乱
	public static MyPoolImpl getInstace(){
		return creatPool.poolImpl;
	}

}



测试类MyPoolMain,测试2000个线程同时请求数据库,程序应对自如,这就是数据库连接池的强大之处。

package com.lcma.main;

import java.sql.ResultSet;
import java.sql.SQLException;

import com.lcma.conn.PooledConnection;
import com.lcma.manager.PoolManager;
import com.lcma.pools.impl.MyPoolImpl;

/**
 * <p>Description: 测试类</p>
 * <p>Copyright  : Copyright (c) 2017</p>
 * @author       : lcma
 * @version      : 1.0
 */
public class MyPoolMain {
	
	/**
	 * 获取连接池容器实现类
	 */
	private static MyPoolImpl poolImpl = PoolManager.getInstace();
	
	/**
	 * 单个连接查询测试
	 */
	public synchronized static void selectData(){
		PooledConnection connection = poolImpl.getConnection();
		ResultSet rs = connection.queryBySql("select * from class");
		try {
			while(rs.next()){
				System.out.print(rs.getString("ID") + "\t\t");
				System.out.print(rs.getString("NAME") + "\t\t");
				System.out.print(rs.getString("TEACHER") + "\t\t");
				System.out.println();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally {
			try {rs.close();} catch (SQLException e) {e.printStackTrace();}
			connection.close();
		}
	}
	
	/**
	 * <p>Discription:测试2000个线程</p>
	 * @param args
	 * @author       : lcma
	 * @update       : 2016年12月5日下午11:44:18
	 */
	public static void main(String[] args){
		for (int i = 0; i < 2000; i++) {
			new Thread(new Runnable() {
				public void run() {
					selectData();
					System.out.println();
				}
			}).start();
		}
		
	}

}



注:上面代码只对功能做出实现,对代码规范性并没有做太高要求。