一、已实现功能
数据库连接缓存。将数据库连接与线程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 idleConnection = new ArrayList<>();
private static ArrayList 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 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。