/**
* Copyright 2009 Genius-Field All Rights Reserved.
*/
package com.geniusfield.sql;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Locale;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import com.geniusfield.GFException;
/**
* The DBConnectionPool class is used to manage the connection pool. For now, it
* only supports JDK 1.6 and JDBC 5.1 for MySQL. We may make it be compatible
* with other versions of JDKs and JDBCs.<BR>
* Use {@link #getInstance()} to get a unique instance or use
* {@link #getInstance(File)} to specify the properties file path.<BR>
* Use {@link #getConn()} to get a connection. You may use it just as usual.
* After using the connection, always use {@link Conn#close()} to free it.<BR>
* There're two scheduled tasks to maintain good health of the connection pool.
* It will check the connection validity and release some waste connections.
*
* @author Junjie Jin
*
*/
public class DBConnectionPool {
private static boolean initialized = false;
private static File file;
private static Properties dbProperties;
private boolean customized;
private String url, port, protocol, classname, dbname, username, password,
connurl;
private int minConn, maxConn, totalConnCount = 0;
private long timeout, autoReleaseTime, checkValidTime;
private LinkedList<Conn> availableConn = new LinkedList<Conn>();
private HashSet<Conn> usedConn = new HashSet<Conn>();
private Timer freeCheck, validCheck;
private static class Instance {
private static DBConnectionPool instance = new DBConnectionPool();
}
/**
* @return a unique instance of DBConnectionPool
* @throws GFException
*/
public static DBConnectionPool getInstance() throws GFException {
if (initialized) {
return Instance.instance;
} else {
if (DBConnectionPool.file == null) {
DBConnectionPool.file = new File("db.properties");
}
DBConnectionPool instance = Instance.instance;
if (initialized) {
return instance;
} else {
throw new GFException("Instance cannot be initialized");
}
}
}
/**
* @param file
* the file containing configuration parameters
* @return a unique instance of DBConnectionPool
* @throws GFException
*/
public static DBConnectionPool getInstance(File file) throws GFException {
if (initialized) {
return Instance.instance;
} else {
DBConnectionPool.file = file;
return DBConnectionPool.getInstance();
}
}
/**
* @param dbProperties
* the properties containing configuration parameters
* @return a unique instance of DBConnectionPool
* @throws GFException
*/
public static DBConnectionPool getInstance(Properties dbProperties)
throws GFException {
if (initialized) {
return Instance.instance;
} else {
DBConnectionPool.dbProperties = dbProperties;
DBConnectionPool instance = Instance.instance;
if (initialized) {
return instance;
} else {
throw new GFException("Instance cannot be initialized");
}
}
}
private DBConnectionPool() {
try {
if (DBConnectionPool.dbProperties == null) {
FileInputStream fis = new FileInputStream(DBConnectionPool.file);
DBConnectionPool.dbProperties = new Properties();
DBConnectionPool.dbProperties.load(fis);
getProperties(DBConnectionPool.dbProperties);
fis.close();
} else {
getProperties(DBConnectionPool.dbProperties);
}
if (!customized) {
connurl = "jdbc:" + protocol + "://" + url + ":" + port + "/"
+ dbname + "?characterEncoding=utf8";
}
Class.forName(classname);
fillPool();
freeCheck = new Timer(true);
validCheck = new Timer(true);
freeCheck.schedule(new FreeCheck(), autoReleaseTime,
autoReleaseTime);
validCheck.schedule(new ValidCheck(), checkValidTime,
checkValidTime);
DBConnectionPool.initialized = true;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
private void getProperties(Properties dbProperties) {
customized = Boolean.parseBoolean(dbProperties.getProperty(
"customized", "false").trim());
connurl = dbProperties.getProperty("connurl",
"jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8")
.trim();
url = dbProperties.getProperty("url", "127.0.0.1").trim();
port = dbProperties.getProperty("port", "3306").trim();
protocol = dbProperties.getProperty("protocol", "mysql").trim()
.toLowerCase(Locale.ENGLISH);
classname = dbProperties.getProperty("classname",
"com.mysql.jdbc.Driver").trim();
dbname = dbProperties.getProperty("dbname", "test").trim();
username = dbProperties.getProperty("username", "root");
password = dbProperties.getProperty("password", "");
minConn = Integer.parseInt(dbProperties.getProperty("minConn", "10")
.trim());
maxConn = Integer.parseInt(dbProperties.getProperty("maxConn", "20")
.trim());
timeout = Long.parseLong(dbProperties.getProperty("timeout", "100")
.trim());
autoReleaseTime = Long.parseLong(dbProperties.getProperty(
"autoReleaseTime", "300000").trim());
checkValidTime = Long.parseLong(dbProperties.getProperty(
"checkValidTime", "14400000").trim());
}
private Conn createConn() throws SQLException {
Connection con = DriverManager.getConnection(connurl, username,
password);
return new Conn(this, con);
}
private synchronized void fillPool() throws SQLException {
while (totalConnCount < minConn) {
availableConn.addLast(createConn());
totalConnCount++;
}
}
/**
* @return a connection to the URL
* @throws SQLException
* @throws GFException
*/
public synchronized Conn getConn() throws SQLException, GFException {
Conn conn;
long startTime = new Date().getTime();
while (true) {
if (!availableConn.isEmpty()) {
conn = availableConn.removeFirst();
conn.setLastUseTime(startTime);
usedConn.add(conn);
return conn;
} else if (totalConnCount < maxConn) {
conn = createConn();
conn.setLastUseTime(startTime);
totalConnCount++;
usedConn.add(conn);
return conn;
} else {
try {
wait(timeout);
if (new Date().getTime() - startTime >= timeout) {
throw new GFException("Getting connection timeout");
} else {
continue;
}
} catch (InterruptedException e) {
throw new GFException("The waiting thread is interrupted");
}
}
}
}
synchronized void freeConn(Conn conn) {
if (usedConn.remove(conn)) {
availableConn.addLast(conn);
notify();
}
}
/**
* @return the number of available connections in the pool
*/
public int getAvailableConnCount() {
return availableConn.size();
}
/**
* @return the number of all the connections generated by the pool
*/
public int getTotalConnCount() {
return totalConnCount;
}
private class FreeCheck extends TimerTask {
public void run() {
Conn conn;
long now = new Date().getTime();
synchronized (DBConnectionPool.this) {
for (Iterator<Conn> i = usedConn.iterator(); i.hasNext();) {
conn = i.next();
if (now - conn.getLastUseTime() > autoReleaseTime) {
i.remove();
availableConn.add(conn);
DBConnectionPool.this.notify();
}
}
}
}
}
private class ValidCheck extends TimerTask {
public void run() {
Conn conn;
synchronized (DBConnectionPool.this) {
for (Iterator<Conn> i = availableConn.iterator(); i.hasNext();) {
conn = i.next();
try {
if (!conn.isValid(1)) {
i.remove();
totalConnCount--;
}
} catch (SQLException e) {
e.printStackTrace();
}
}
try {
fillPool();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}