一个简单数据库连接池的实现

一、已实现功能

  数据库连接缓存。将数据库连接与线程ID绑定并提供执行数据库操作时检测。数据库连接超时检测。初始化数据库环境,包括初始化数据库,数据库用户,数据库表。

二、代码列表:

1、MySqlDBManager:

  用于管理数据库配置、初始化数据库环境及创建数据库连接等操作。

2、ConnectionAdapter:

  数据库连接适配,封装了具体数据库连接,在现有功能上新增与线程ID绑定、连接超时检测等功能。

3、ConnectionException:

  数据库异常,简单继承自SQLException,目前没有具体实现。

4、ConnectionPool:

  数据库连接池具体实现,数据库连接出入栈及释放所有连接操作。

5、ITable:

  一个表的超类,只有两个函数:判断表存在(tableIsExist)、创建表(createTable)。

6、DBConnectionFactory:

  数据库连接工厂,唯一对外接口:获取连接(getConnection)、初始化数据库上下文(initDataBaseContext)、关闭所有连接(closeAllConnection)。

三、代码设计

  1、MySqlDBManager:此类只被DBConnectionFactory调用,初始化主要包含:

  • 检测数据库及账户是否存在 
  • 检测数据库中表是否存在

主要实现的函数:

  • getConnection: 从数据库连接池中获取一个连接并返回。
  • closeAllConnection: 释放所有连接。
  • createNewConnection:函数为默认作用域,在DBConnectFactory中调用,创建一个新的数据库连接。
class MySqlDBManager{
	private final static String TAG = "MysqlDBConnectionManager";
	private final static String driverClassName = "com.mysql.jdbc.Driver";
	private final String database = "blog";
	private final String URL = "jdbc:mysql://localhost:3306/" + database + "?useSSL=false";
	private final String USERNAME = "****";
	private final String PASSWORD = "****";
	private final String createDatabase = "create DATABASE "+ database +" CHARACTER SET utf8;";
	private final String createUser = "create USER '" + USERNAME + "'@'localhost' IDENTIFIED BY '" + PASSWORD + "';";
	private final String grantUser = "GRANT ALL PRIVILEGES ON " + database + ".* TO '" + USERNAME + "'@'localhost';";
	private final String flush = "FLUSH PRIVILEGES;";
	
	private final String ROOT_URL = "jdbc:mysql://localhost:3306/mysql?useSSL=false";
	private final String ROOT_USERNAME = "root";
	private final String ROOT_PASSWORD = "*******";
	
	//private final static int intMaxConnectionNum = 50;
	//private int intConnectionNum = 0;

	private static ConnectionPool connPool = new ConnectionPool();

	MySqlDBManager(){
		try{
			Class.forName(driverClassName);			
		} catch(ClassNotFoundException ce){
			ce.printStackTrace();
		}
		initDatabase();
	}
	
	public Connection getConnection() {
		// TODO Auto-generated method stub
		return connPool.pop();
	}

	public void closeAllConnection() {
		connPool.releaseAllConnection();
	}
	
	private void initDatabase(){
		if(!DatabaseIsExist()){
			switchRootInitDatabase();
		}
	}

	private boolean DatabaseIsExist(){
		boolean result = false;
		Connection conn = null;
		try {
			conn = (Connection) DriverManager.getConnection(URL, USERNAME, PASSWORD);
			result = true;
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			//e.printStackTrace();
		} finally{
			try {
				if(conn != null){
					conn.close();
				}
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return result;
	}
	
	private void switchRootInitDatabase(){
		try {
			Connection conn = DriverManager.getConnection(ROOT_URL, ROOT_USERNAME, ROOT_PASSWORD);
			Statement smt = conn.createStatement();
			smt.addBatch(createDatabase);
			smt.addBatch(createUser);
			smt.addBatch(grantUser);
			smt.addBatch(flush);
			smt.executeBatch();
			smt.close();
			conn.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void initTable(ITable tab){
		if(!tab.tableIsExist(getConnection())){
			tab.createTable(getConnection());
		}
	}
	
	public boolean tableIsExist(ITable tab){
		return tab.tableIsExist(getConnection());
	}
		
	Connection createNewConnection(){
		Connection conn = null;
		try {
			conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(TAG + ",createNewConnection()!");
		return conn;
	}
}

 

2、ConnectionPool

数据库连接池,包含一个idleList和usedList。

主要函数:

push:当应用使用完数据库连接,调用close方法时,将数据库连接放回连接池。

pop:当显示调用DBConnectionFactor.geConnection方法时,返回一个数据库连接给应用。

releaseAllConnection:释放所有数据库连接,因为此实现设计中存在一个双向关联(connectionPool持有ConnectionAdapter引用,ConnectionAdapter持有connectionPool引用),必须在释放操作最后调用idleList.Clear和usedList.Clear。

class ConnectionPool {
	private final static String TAG = "ConnectionPool";
	private static ArrayList<ConnectionAdapter> idleConnection = new ArrayList<>();
	private static ArrayList<ConnectionAdapter> usedConnection = new ArrayList<>();
	
	public ConnectionPool(){}
	
	void push(ConnectionAdapter connAdapter){
		// TODO Auto-generated method stub
		synchronized(ConnectionPool.class){
			if(connAdapter != null){
				usedConnection.remove(connAdapter);
			}
			
			if(connAdapter != null && !idleConnection.contains(connAdapter)){
				idleConnection.add(connAdapter);
			}
		}
		System.out.println(TAG + ",idle connection number: " + idleConnection.size());
		System.out.println(TAG + ",used connection number: " + usedConnection.size());
	}
	
	public Connection pop(){
		synchronized(ConnectionPool.class){
			ConnectionAdapter connAdapter = null;
			if(idleConnection.isEmpty()){
				connAdapter = createNewConnectionAdapter();
			}else{
				connAdapter = idleConnection.get(0);
				idleConnection.remove(connAdapter);
				if(connAdapter == null || connAdapter.isInvalid()){
					connAdapter = createNewConnectionAdapter();
				}
			}
			//System.out.println(TAG + ",pop()");
			if(connAdapter != null && !usedConnection.contains(connAdapter)){
				usedConnection.add(connAdapter);
			}
			return connAdapter.getProxyConnection();
		}
	}

	private ConnectionAdapter createNewConnectionAdapter(){
		return DBConnectionFactory.createNewConnectionAdapter(ConnectionPool.this);
	}
	
	public void releaseAllConnection() {
		// TODO Auto-generated method stub
		Iterator<ConnectionAdapter> it = idleConnection.iterator();
		while(it.hasNext()){
			it.next().Close();
		}
		
		it = usedConnection.iterator();
		while(it.hasNext()){
			it.next().Close();
		}
		idleConnection.clear();
		usedConnection.clear();
	}
}

3、ConnectionAdapter

数据库适配器,新增两个功能:将数据库连接与线程ID绑定,MYSQL超时检测。

新增功能解释:

1、与线程ID绑定

在实际使用过程中发现存在如下一个情况,假设两个线程同时进行数据库操作时,线程A获取一个数据库连接conn_1,当执行完操作以后,调用conn_1.colse,将数据库连接返回数据库连接池后,其实线程A依然是持有Conn_1的引用的,如果此时线程A继续使用conn_1进行数据库操作,函数将正常执行。如果此时线程B从数据库连接池获取一个空闲连接等到conn_1,那么这时候将两个线程将同时持有同一个数据库连接。解决方案如下:在ConnectionAdapter中保存一个当前持有该连接的线程ID,在操作执行之前比对线程ID,如果非持有线程执行的数据库操作,提示该连接已经关闭。

实现此功能也为以后实现连接池超时回收连接考虑,超时回收基于功能可以简单实现。

2、MYSQL超时检测

MYSQL数据库默认配置存在一个8小时自动关闭超时连接,当一个连接超过8小时没有使用,会被MYSQL关闭,如果在此连接上执行数据库操作,会出现异常。

主要函数:

  • getProxyConnection:获取一个代理连接。
  • Close:关闭数据库连接。此函数只被ConnectionPool连接调用,用于释放连接。
  • isInvalid:连接有效性判断,包括超时判断。
  • markUsed,markIdle:连接标记为空闲或者正在使用。
  • checkStatus;在此连接上执行任何操作之前进行检测,目前主要检测线程ID。
  • _Connection:一个简单的代理。
class ConnectionAdapter {
	private final long connectionIimeout = 8 * 60 * 60 * 1000 - 10 * 60 * 1000;//减去十分钟左右误差时间
	private long lastIimeoutTest = 0L;
	private boolean isIdle = true;
	private long ownerThreadId = -1L;
	private Connection conn;
	private Connection proxyConn;
	
	public ConnectionAdapter(Connection conn,ConnectionPool pool){
		this.conn = conn;
		this.proxyConn = (Connection) Proxy.newProxyInstance(conn.getClass().getClassLoader(), new Class[]{Connection.class}, new _Connection(conn,pool));
		this.lastIimeoutTest = System.currentTimeMillis();
	}

	public Connection getProxyConnection(){
		if(markUsed()){
			return proxyConn;
		}else{
			return null;
		}
	}
	
	public void Close(){
		try {
			if( conn != null && !conn.isClosed()){
				conn.close();
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public boolean isInvalid(){
		if(conn == null){return true;}
		if(proxyConn == null){return true;}
		if(connectionIsWaitTimeout()){return true;}
		
		try {
			if(!conn.isClosed()){
				return false;
			}
		} catch (SQLException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		return true;
	}
	
	private Boolean connectionIsWaitTimeout(){
		boolean result = true;
		if((System.currentTimeMillis() - lastIimeoutTest) >  connectionIimeout){
			result = testConnectionIsOk();
		}else{
			result = false;
		}
		lastIimeoutTest = System.currentTimeMillis();
		return result;
	}
	
	private boolean testConnectionIsOk(){
		try{
			PreparedStatement stat = conn.prepareStatement("select 1 from users where 1=2");
			stat.execute();
			stat.close();
			return true;
		} catch (CommunicationsException e){
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}
	
	private boolean markUsed(){
		if(!isIdle){
			return false;
		}
		isIdle = false;
		ownerThreadId = Thread.currentThread().getId();
		return true;
	}
	
	private boolean markIdle() throws Exception{
		if(isIdle){return false;}
		
		if(ownerThreadId != Thread.currentThread().getId()){
			throw new ConnectionException("Current ThreadId is " + Thread.currentThread().getId() + ",but the connection is used by " + ownerThreadId + " Thread!");
		}
		
		isIdle = true;
		ownerThreadId = -1;
		return true;
	}
	
	private void checkStatus() throws Exception{
		if(isIdle){
			throw new ConnectionException("this connection is closed!");
		}
		if(ownerThreadId != Thread.currentThread().getId()){
			throw new ConnectionException("Current ThreadId is " + Thread.currentThread().getId() + ",but the connection is used by " + ownerThreadId + " Thread!");
		}
	}

	private boolean isClosed(){
		if(isIdle){return true;}
		if(ownerThreadId != Thread.currentThread().getId()){return true;}
		return false;
	}
	
	private class _Connection implements InvocationHandler{
		private final Connection conn;
		private final ConnectionPool pool;
		
		_Connection(Connection conn,ConnectionPool pool){
			this.conn = conn;
			this.pool = pool;
		}
		
		@Override
		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			// TODO Auto-generated method stub
			Object obj = null;
			if("close".equals(method.getName())){
				if(markIdle()){
					pool.push(ConnectionAdapter.this);
				}
			}else if("isClosed".equals(method.getName())){
				obj = isClosed();
			}else{
				checkStatus();
				obj = method.invoke(conn, args);
			}
			return obj;
		}
	}
}

4、ITable

数据库表超类,用户初始化数据库表及基础数据,因为单独创建数据库表示一件很繁琐的事情,如果可以在应用启动的时候检测数据库表是否存在,并完成一些基础数据的初始化,会减轻很多工作。

public abstract class ITable {
	public boolean tableIsExist(Connection conn){
		boolean result = true;
		try{
			PreparedStatement stat = conn.prepareStatement(getTestExistStatement());
			stat.execute();
			stat.close();
		} catch (CommunicationsException e){
			result = false;
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			result = false;
		} finally{
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return result;
	};
	
	public void createTable(Connection conn){
		Statement smt;
		try {
			smt = conn.createStatement();
			String[] str = getCreateStatement();
			for(int k = 0; k < str.length; k++){
				smt.addBatch(str[k]);
			}
			smt.executeBatch();
			smt.close();
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally{
			try {
				conn.close();
			} catch (SQLException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	protected abstract String getTestExistStatement();
	protected abstract String[] getCreateStatement();
}

四、总结

  作为一个完整的实现,对外唯一接口为DBConnectionFactory,所以外部应只调用DBConnectionFactory中的函数,不应直接调用其他类的函数。

  代码贴在这里好像显得比较乱,所以只贴了几个主要实现的代码。

项目代码:https://github.com/hu-xuemin/mapper.git。

转载于:https://www.cnblogs.com/huxuemin/p/6427394.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值