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

来源:互联网 发布:兰灯翻墙for mac 编辑:程序博客网 时间:2024/06/05 15:19

一、已实现功能

  数据库连接缓存。将数据库连接与线程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 stubreturn 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 blocke.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 blocke.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 blocke.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 stubsynchronized(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 stubIterator<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 blocke.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 blocke1.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 blocke.printStackTrace();} catch (SQLException e) {// TODO Auto-generated catch blocke.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;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// TODO Auto-generated method stubObject 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 blockresult = false;} finally{try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.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 blocke.printStackTrace();} finally{try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}protected abstract String getTestExistStatement();protected abstract String[] getCreateStatement();}
四、总结

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

  代码贴在这里好像显得比较乱,所以只贴了几个主要实现的代码,想查看完整代码可以到下面几个链接查看。

五、项目代码

完整代码及实际使用:https://github.com/hu-xuemin/xBlog.git。其中com.huxuemin.xblog.database包中为此文相关代码。




0 0
原创粉丝点击