import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import org.apache.log4j.jdbc.JDBCAppender;
import org.apache.log4j.spi.LoggingEvent;
import com.gftech.util.GFConn;
import com.gftech.util.GFDB;
public class JDBCExtAppender extends JDBCAppender {
protected String driver;
public static GFDB gfdb;
private ArrayList
public JDBCExtAppender() {
super();
tempList = new ArrayList
}
/**
* Override this to return the connection to a pool, or to clean up the
* resource.
*
* The default behavior holds a single connection open until the appender is
* closed (typically when garbage collected).
*/
protected void closeConnection(Connection con) {
if (con != null && tempList != null) {
for (int i = 0; i < tempList.size(); i++) {
GFConn gfconn = tempList.get(i);
if (con == gfconn.getConn()) {
gfconn.close();
tempList.remove(i);
// System.err.println("remove conn:"+con);
break;
}
}
}
}
/**
* Override this to link with your connection pooling system.
*
* By default this creates a single connection which is held open until the
* object is garbage collected.
*/
protected Connection getConnection() throws SQLException {
if (gfdb == null) {
gfdb = new GFDB("db9", driver, databaseURL, databaseUser, databasePassword);
}
if (gfdb != null) {
GFConn gfconn = gfdb.getConn();
if (gfconn != null) {
connection = gfconn.getConn();
tempList.add(gfconn);
}
}
return connection;
}
public void close() {
flushBuffer();
this.closed = true;
}
public String getLogStatement(LoggingEvent event){
StringBuffer sbuf=new StringBuffer();
sbuf.append(layout.format(event));
if (layout.ignoresThrowable ()) {
sbuf.delete(sbuf.length()-2,sbuf.length() );
String[] s = event.getThrowableStrRep();
if (s != null) {
for (int j = 0; j < s.length; j++) {
sbuf.append("/r/n");
sbuf.append(s[j]);
}
}
sbuf.append("')");
}
return sbuf.toString() ;
}
public void setDriver(String driverClass) {
driver = driverClass;
}
数据库连接池的主要源代码如下:
public class GFDB {
private String dbName;
private ArrayList
private ArrayList
private final int maxConns = 150;// 最大连接数目
private final int maxUserCount = 149;// 每个连接允许的最大用户数目
private final int timeout = 60000;// 连接的最大空闲时间
private final int waitTime = 30000;// 用户的最大等待时间
private String dbUrl = null;// 连接地址
private String dbDriver = null;// 数据库驱动
private String dbUser = null;// 登陆用户名
private String dbPwd = null;// 登陆密码
private ThreadGroup group = null;
static Logger logger=Logger.getLogger(GFDB.class);
public GFDB(String dbName, String driver, String url, String user,
String pwd) {
this.dbName = dbName;
dbDriver = driver;
dbUrl = url;
dbUser = user;
dbPwd = pwd;
if (driver != null && url != null && user != null && pwd != null)
init();
}
private void init() {
Connection conn = null;
GFConn _conn = null;
// init the connection pool
idleConnPool = new ArrayList
usingConnPool = new ArrayList
conn = buildConn();
if (conn != null) {
_conn = new GFConn();
_conn.setConn(conn);
idleConnPool.add(_conn);
} else {
try {
logger.error("/n/n和数据库的连接被重置,试图重新建立连接。。。");
Thread.sleep(10000);
init();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
if (group == null)
group = new ThreadGroup("ThreadGroup");
// if the manager thread is not active ,then run it
if (!isActive(group, "manager"))
manager();
}
/**
* 连接远程数据库
*
* @return 连接成功返回TRUE
*/
private Connection buildConn() {
Connection conn = null;
try {
Class.forName(dbDriver);
conn = DriverManager.getConnection (dbUrl, dbUser, dbPwd);
if (conn != null) {
String str = "建立和远程数据库" + dbUrl + "的连接:" + conn;
logger.info(str);
}
} catch (SQLException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return conn;
}
/**
* 从连接池中取出一个可用的连接. 如果空闲池中有,则从空闲池中取,然后把userCount加一,并把此连接
* 移动使用池中;否则,判断使用池中是否有可用的连接(一个连接上 的使用用户数目还没有达到最大值即为可用的连接),如果有则在此
* 连接分配给用户使用,如果使用池中没有可用的连接,并连接的总数目 还没有达到系统限制的最大值,则重新创建一个连接供用户使用,否则
* 等待一段时间再查看是否有可用连接。
*
* @param timeout
* 最大等待时间,超出此时间则返回NULL
* @return
*/
public synchronized GFConn getConn() {
Connection conn = null;
GFConn _conn = null;
if (idleConnPool != null && idleConnPool.size() > 0) {
// 这个地方可能会出数组越界异常. 估计还是同步问题
_conn = (GFConn) idleConnPool.remove(0);
// System.out.println("remove from idleConnPool:"+_conn);
_conn.setLastAccessTime();
_conn.changeUserCount(1);
usingConnPool.add(_conn);
return _conn;
} else {
if (usingConnPool != null) {
if (usingConnPool.size() > 0) {
// System.out.println ("usingConnPool.size():"+usingConnPool.size());
for (int i = 0; i < usingConnPool.size(); i++) {
_conn = (GFConn) usingConnPool.get(i);
if (_conn.getUserCount() < maxUserCount) {
_conn.changeUserCount(1);
return _conn;
}
}
_conn = null;
}
// 如果目前没有可用的连接并且没有达到最大的连接数目,则重新为用户
// 创建一个连接,否则等待一段时间看是否有连接被释放。
if (usingConnPool.size() < maxConns) {
conn = buildConn();
if (conn != null) {
_conn = new GFConn();
_conn.setConn(conn);
_conn.changeUserCount(1);
usingConnPool.add(_conn);
return _conn;
}
} else {
try {
Thread.sleep(waitTime);
System.out.println("No usable connection,sleep...");
} catch (InterruptedException e) {
e.printStackTrace();
}
getConn();
}
}
}
return null;
}
。。。。
}
主类里的调用代码:
PropertyConfigurator.configure(ConfParam.LOG4J_PROP_CONF);
logger.info("load dict");
问题出来了,在程序启动后,只要一执行完logger.info("load dict");这一句,系统就不停的向数据发起连接,至到资源耗尽为止,百思不得其解,程序原来也是一直是正常的,不管是向文件输出还是向远程数据库输出日志信息都没有问题,现在为何会限入死循环呢?
仔细分析源代码可以发现,程序在执行logger.info("load dict");后,为了按照LOG4J。properties中定义的数据库输出执行,log4j接口会自动调用JDBCExtAppender当中的getConnection()方法,在此方法中才会创建数据库实例并创建数据库连接。在此方法中执行了这么一句:
gfdb = new GFDB("db9", driver, databaseURL, databaseUser, databasePassword);
我在调试中发现,只要执行到这一句之后就开始陷入死循环,程序不往下执行获取连接那一步,那说明问题是出在new GFDB()这个地方,可是奇怪的是我以前是正常的呀,并且如果不起用JDBCExtAppender也不会出现这个的问题。再跳到GFDB类里分析,构造函数里面调用了一个init()方法,而init()里面又调用了buildConn()方法,问题就表现在buildConn()里面。
经过研究发现,在创建连接成功后我调用logger.info()输出了一个创建成功的提示,见上面红色代码,把它屏蔽结果就好了。再仔细琢磨这个方法,又对照执行时打印出来的异常信息是个死循环的引用恍然大悟,死循环就出现在这里。
原因很简单,我在创建连接成功后,调用了logger。info()方法,而一调用此方法logger为了把日志输出到数据库它就立即又去调用JDBCExtAppender当中的getConn ection()方法,而此时GFDB的实例还没有被创建成功,所以gfdb=null,于是该方法又去调用new GFDB去创建连接,创建成功后又会调用logger。info()。。。
如此反复,就彻底进入了死循环的怪圈,至到系统资源耗尽。解决的办法很简单,就是在数据库连接池类里面不要用logger来输出日志就行了,全部改成System.out.println()输出,其实原来我就是这样,所以一直挺正常。前几天为了把输出统一起来,也为了能够记录数据库创建的信息,就把标准输出改成了logger输出,导致了现在这一情况的出现。不过有了这样的教训,也有了一个教训,不然自己引用自己,象小猫玩自己抓自己尾巴的游戏,总也抓不住,累都累死。