从零到一实现数据库连接池

目标:

  实现数据库连接池的基本功能,包括但不限于获取数据库连接、维持数据库连接、释放数据库连接。

实现:

思路:

  自定义ConnectionPool类,完成数据库连接建立、维持、释放的功能。大致步骤如下:

  • 首先初始化时,新建数据库连接,同时在容器中存储用户定义的initConnections个数量的数据库连接。
  • 用户获取连接时,首先判断freeConnection空闲连接的数量是否大于0,如果大于,则直接返回连接。如果小于则判断当前连接数量是否小于maxActiveConnections最大允许的连接数,如果不小于,则自我阻塞并等待,如果小于则新建一个新的连接返回给用户。
  • 用户释放连接时,首先判断空闲连接池中连接数量是否大于maxConnections空闲池最大允许保有量,如果不大于,则直接放入到空闲连接池中。同时将活动连接池中连接删除。如果大于则需要释放该连接,并通知阻塞的连接获取连接。

一、具体开发流程

  实现数据库连接建立、维持、释放的功能。

1、加入依赖

  本项目是自定义连接池,需要对数据库访问,所以需要添加MySQL驱动包。源码如下:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>handwritingproject</artifactId>
        <groupId>com.njust</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>databaseConnection</artifactId>
    <dependencies>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>
    </dependencies>

</project>

2、自定义配置类

  定义DbBean类,主要存储数据库连接池的基本信息、供用户个性化配置。如数据库用户名、密码、连接池名称、空闲池最大连接数等基本信息。源码如下:

DbBean.java

package com.njust.connection;

//外部配置文件信息
public class DbBean {

	/* 链接属性 */
	private String driverName = "com.mysql.jdbc.Driver";

	private String url = "jdbc:mysql://localhost:3306/mysql_learning?useUnicode=true&characterEncoding=UTF8";

	private String userName = "root";

	private String password = "root";

	private String poolName = "ChenConnectionPool";// 连接池名字

	private int minConnections = 1; // 空闲池,最小连接数

	private int maxConnections = 10; // 空闲池,最大连接数

	private int initConnections = 5;// 初始化连接数

	private long connTimeOut = 1000;// 重复获得连接的频率

	private int maxActiveConnections = 100;// 最大允许的连接数,和数据库对应

	private long connectionTimeOut = 1000 * 60 * 20;// 连接超时时间,默认20分钟

	public String getDriverName() {
		return driverName;
	}

	public void setDriverName(String driverName) {
		this.driverName = driverName;
	}

	public String getUrl() {
		return url;
	}

	public void setUrl(String url) {
		this.url = url;
	}

	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 String getPoolName() {
		return poolName;
	}

	public void setPoolName(String poolName) {
		this.poolName = poolName;
	}

	public int getMinConnections() {
		return minConnections;
	}

	public void setMinConnections(int minConnections) {
		this.minConnections = minConnections;
	}

	public int getMaxConnections() {
		return maxConnections;
	}

	public void setMaxConnections(int maxConnections) {
		this.maxConnections = maxConnections;
	}

	public int getInitConnections() {
		return initConnections;
	}

	public void setInitConnections(int initConnections) {
		this.initConnections = initConnections;
	}

	public long getConnTimeOut() {
		return connTimeOut;
	}

	public void setConnTimeOut(long connTimeOut) {
		this.connTimeOut = connTimeOut;
	}

	public int getMaxActiveConnections() {
		return maxActiveConnections;
	}

	public void setMaxActiveConnections(int maxActiveConnections) {
		this.maxActiveConnections = maxActiveConnections;
	}

	public long getConnectionTimeOut() {
		return connectionTimeOut;
	}

	public void setConnectionTimeOut(long connectionTimeOut) {
		this.connectionTimeOut = connectionTimeOut;
	}

}

3、自定义连接池接口

  定义IConnectionPool类,对外提供获取连接getConnection(),和释放连接releaseConnection()的接口。源码如下:

IConnectionPool.java

package com.njust.connection;

import java.sql.Connection;

//连接数据库池
public interface IConnectionPool {

	// 获取连接(重复利用机制)
	public Connection getConnection();

	// 释放连接(可回收机制)
	public void releaseConnection(Connection connection);
}

4、连接池实现类

  新建ConnectionPool类,该类主要完成数据库连接建立、维持、释放的功能。大致步骤如下:首先初始化时,新建数据库连接,同时在容器中存储用户定义的initConnections个数量的数据库连接。用户获取连接时,首先判断freeConnection空闲连接的数量是否大于0,如果大于,则直接返回连接。如果小于则判断当前连接数量是否小于maxActiveConnections最大允许的连接数,如果不小于,则自我阻塞并等待,如果小于则新建一个新的连接返回给用户。用户释放连接时,首先判断空闲连接池中连接数量是否大于maxConnections空闲池最大允许保有量,如果不大于,则直接放入到空闲连接池中。同时将活动连接池中连接删除。如果大于则需要释放该连接,并通知阻塞的连接获取连接。源码如下:

ChenTransaction.java

package com.njust.connection;

import java.sql.Connection;
import java.sql.DriverManager;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicInteger;

public class ConnectionPool implements IConnectionPool {
	// 使用线程安全的集合 空闲线程 容器 没有被使用的连接存放
	private List<Connection> freeConnection = new Vector<Connection>();
	// 使用线程安全的集合 活动线程 容器 容器正在使用的连接
	private List<Connection> activeConnection = new Vector<Connection>();
	private DbBean dbBean;

	private AtomicInteger countConne = new AtomicInteger(0);

	public ConnectionPool(DbBean dbBean) {
		// 获取配置文件信息
		this.dbBean = dbBean;
		init();
		System.out.println();
	}

	// 初始化线程池(初始化空闲线程)
	private void init() {
		if (dbBean == null) {
			return;// 注意最好抛出异常
		}
		// 1.获取初始化连接数
		for (int i = 0; i < dbBean.getInitConnections(); i++) {
			// 2.创建Connection连接
			Connection newConnection = newConnection();
			if (newConnection != null) {
				// 3.存放在freeConnection集合
				freeConnection.add(newConnection);
			}
		}

	}

	// 创建Connection连接
	private  Connection newConnection() {
		try {
//			装载一个类并且对其进行实例化的操作。
			Class.forName(dbBean.getDriverName());
			Connection connection = DriverManager.getConnection(dbBean.getUrl(), dbBean.getUserName(),
					dbBean.getPassword());
			countConne.incrementAndGet();
			return connection;
		} catch (Exception e) {
			return null;
		}

	}

	// 调用getConnection方法 --- 获取连接
	public  Connection getConnection() {

		try {
			Connection connection = null;
			// 思考:怎么知道当前创建的连接>最大连接数
			if (countConne.get() < dbBean.getMaxActiveConnections()) {
				// 小于最大活动连接数
				// 1.判断空闲线程是否有数据
				if (freeConnection.size() > 0) {
					// 空闲线程有存在连接
					// ==freeConnection.get(0);freeConnection.remove(0)
					// 拿到在删除
					connection = freeConnection.remove(0);
				} else {
					// 创建新的连接
					connection = newConnection();
				}
				// 判断连接是否可用
				boolean available = isAvailable(connection);
				if (available) {
					// 存放在活动线程池
					activeConnection.add(connection);
				} else {
					connection = getConnection();// 怎么使用重试? 递归算法
				}

			} else {
				// 大于最大活动连接数,进行等待
				wait(dbBean.getConnTimeOut());
				// 重试
				connection = getConnection();
			}
			return connection;
		} catch (Exception e) {
			return null;
		}

	}

	// 判断连接是否可用
	public boolean isAvailable(Connection connection) {
		try {
			if (connection == null || connection.isClosed()) {
				return false;
			}
		} catch (Exception e) {
		}
		return true;

	}

	// 释放连接 回收
	public synchronized void releaseConnection(Connection connection) {
		try {
			// 1.判断连接是否可用
			if (isAvailable(connection)) {
				// 2.判断空闲线程是否已满
				if (freeConnection.size() < dbBean.getMaxConnections()) {
					// 空闲线程没有满
					freeConnection.add(connection);// 回收连接
				} else {
					// 空闲线程已经满
					connection.close();
					countConne.decrementAndGet();
				}
				activeConnection.remove(connection);
				notifyAll();
			}
		} catch (Exception e) {
			// TODO: handle exception
		}

	}

	public int getCountConne() {
		return countConne.get();
	}
}

5、线程池管理类

  新建ConnectionPoolManager类,单例实现线程池的管理,防止过多实例创建导致内存溢出。源码如下:

ConnectionPoolManager .java

package com.njust.connection;

import java.sql.Connection;

// 管理线程池
public class ConnectionPoolManager {
	private static DbBean dbBean = new DbBean();
	private static ConnectionPool connectionPool = new ConnectionPool(dbBean);

	// 获取连接(重复利用机制)
	public static Connection getConnection() {
		return connectionPool.getConnection();
	}

	// 释放连接(可回收机制)
	public static void releaseConnection(Connection connection) {
		connectionPool.releaseConnection(connection);
	}

	public static int getCount() {
		return connectionPool.getCountConne();
	}
}

6、测试类

  新建Test001类,其中ThreadConnection 不停的获取和释放连接,主线程新建30个这样的线程,压力测试数据库连接池的性能。源码如下:

Test001 .java

package com.njust.connection;

import java.sql.Connection;

public class Test001 {

    public static void main(String[] args) {
        ThreadConnection threadConnection = new ThreadConnection();
        for (int i = 1; i < 30; i++) {
            Thread thread = new Thread(threadConnection, "线程i:" + i);
            thread.start();
        }

    }

}

class ThreadConnection implements Runnable {

    public void run() {
        for (int i = 0; i < 100; i++) {
            Connection connection = ConnectionPoolManager.getConnection();
            System.out.println(Thread.currentThread().getName() + ",connection:" + connection);
            ConnectionPoolManager.releaseConnection(connection);
        }

        System.out.println(Thread.currentThread().getName() + " : " + ConnectionPoolManager.getCount());
    }

}

  由于输出信息过多,我暂时截取部分输出。我们可以发现connection:com.mysql.jdbc.JDBC4Connection@16614a20连接被复用了多次。有效的减少了数据库连接释放的资源消耗。控制台输出如下:

Console


线程i:2,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:2,connection:com.mysql.jdbc.JDBC4Connection@4d59c246
线程i:2,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:2,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:26,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:7 : 5
...
线程i:24,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:24,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:24,connection:com.mysql.jdbc.JDBC4Connection@7b0dfcb9
线程i:24,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:24,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@42903f23
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@7b0dfcb9
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@42903f23
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@7b0dfcb9
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@42903f23
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@50230424
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@42903f23
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@14adf04d
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@6cfc31a0
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@4d59c246
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@42903f23
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@50230424
线程i:6,connection:com.mysql.jdbc.JDBC4Connection@7b0dfcb9
线程i:24,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:6 : 9
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@4d59c246
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@50230424
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@6cfc31a0
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@14adf04d
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@4d59c246
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@7b0dfcb9
线程i:24,connection:com.mysql.jdbc.JDBC4Connection@42903f23
线程i:4,connection:com.mysql.jdbc.JDBC4Connection@6cfc31a0
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@50230424
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@4d59c246
线程i:4 : 9
线程i:24,connection:com.mysql.jdbc.JDBC4Connection@14adf04d
线程i:24 : 9
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@6cfc31a0
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@42903f23
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@7b0dfcb9
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@50230424
线程i:14,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:14 : 9
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@416d0bfc
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@4d59c246
线程i:1,connection:com.mysql.jdbc.JDBC4Connection@14adf04d
线程i:1 : 9
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@16614a20
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@6cfc31a0
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@42903f23
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@7b0dfcb9
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@50230424
线程i:5,connection:com.mysql.jdbc.JDBC4Connection@14c448fe
线程i:5 : 9

Process finished with exit code 0

总结

获取连接

流程图

Created with Raphaël 2.2.0 开始 获取连接 判断freeConnection 空闲连接的数量是否大于0? 从freeConnection中取出连接并返回 结束 当前连接数量是否小于 maxActiveConnections 最大允许的连接数 新建一个数据库连接并返回 自我阻塞并等待 yes no yes no

释放连接

流程图

Created with Raphaël 2.2.0 开始 释放连接 判断空闲连接池中连接数量是否大于 maxConnections空闲池最大允许保有量? 释放该数据库连接 通知阻塞的线程获取数据库连接 结束 将数据库连接直接放入到空闲连接池中, 同时将活动连接池中连接删除 yes no

重点及易错点

1、多线程资源竞争问题

	public synchronized void releaseConnection(Connection connection) {
		try {
			// 1.判断连接是否可用
			if (isAvailable(connection)) {
				// 2.判断空闲线程是否已满
				if (freeConnection.size() < dbBean.getMaxConnections()) {
					// 空闲线程没有满
					freeConnection.add(connection);// 回收连接
				} else {
					// 空闲线程已经满
					connection.close();
					countConne.decrementAndGet();
				}
				activeConnection.remove(connection);
				notifyAll();
			}
		} catch (Exception e) {
			// TODO: handle exception
		}

	}

  在释放资源的时候一定要调用notifyAll();方法通知阻塞线程获取连接,否则阻塞线程将一直等待。释放方法一定要加synchronized属性,否则会出现死锁问题。

2、记录连接变量

private AtomicInteger countConne = new AtomicInteger(0);

  一定要使用线程安全的原子变量记录当前连接数量,否则会出现并发安全问题。
  有问题欢迎各位读者批评指正。

点个赞再走呗!欢迎留言哦!

发布了22 篇原创文章 · 获赞 4 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/qq_32510597/article/details/105280519