现在的位置: 首页 > 综合 > 正文

数据库连接池的设计思路及java实现

2013年09月18日 ⁄ 综合 ⁄ 共 8710字 ⁄ 字号 评论关闭

connectionPool.DBConnectionManager

package connectionPool;

import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import connectionPool.util.Logger;
import connectionPool.util.PropertiesMgr;

/**
 * 连接池的设计思路
 * 1、
 * 初始化固定数目的连接(空闲连接与活跃连接在同一池中),建立连接的代理类,添加busy与startTime属性以便分发与回收连接
 * 另建立守护线程回收失效连接
 * 2、
 * 维护一空闲连接池,初始为空,需要连接时建立,用完的连接回收进入空闲连接池;
 * 后续所需连接从空闲连接池获取;activeNum记录活跃连接数目;
 * 当空闲连接池为空且活跃连接数达到上限时,请求等待,超时即获取连接失败,超时前有连接被释放方可获得连接
 * 第二个设计巧妙优势明显,采用第二种方式
 * 
 * 数据库连接管理类,单例模式
 * 可以管理加载多个数据库驱动,维护多个数据库连接池
 * @author shijin
 *
 */
public class DBConnectionManager {
	
	private static DBConnectionManager dbm = null;
	
	//单例模式里的成员变量都相当于是static了?
	/**
	 * 客户数目
	 */
	private static int clients = 0;
	/**
	 * 加载的驱动器集合
	 */
	private Set<Driver> drivers = new HashSet<Driver>();
	/**
	 * 数据库连接池字典
	 */
	private Hashtable<String,DBConnectionPool> pools = new Hashtable<String,DBConnectionPool>();
	
	private final Logger log = Logger.getInstance(DBConnectionPool.class);
	
	private DBConnectionManager() {
		loadDrivers();
		createPools();
	}

	/**
	 * 装载和注册所有的JDBC驱动程序
	 */
	private void loadDrivers() {
		String str_drivers = PropertiesMgr.getProperty("driver");
		for(String str_driver:str_drivers.split("\\s")) {
			Driver driver = null;
			try {
				driver = (Driver)Class.forName(str_driver).newInstance();
				DriverManager.registerDriver(driver);
				log.info("成功加载JDBC驱动:" + str_driver);
			} catch (InstantiationException e) {
				log.error("加载JDBC驱动" + str_driver + "时初始化异常,请检查配置文件");
			} catch (IllegalAccessException e) {
				log.info("加载JDBC驱动" + str_driver + "时非法访问,请检查配置文件");
			} catch (ClassNotFoundException e) {
				log.info("未找到JDBC驱动" + str_driver + "请引入相关包");
			} catch (SQLException e) {
				log.info("加载JDBC驱动" + str_driver + "失败,请检查引入包的正确性");
			}
			drivers.add(driver);
		}
	}

	/**
	 * 根据配置文件创建数据库连接池
	 */
	private void createPools() {
		Enumeration<?> elements = PropertiesMgr.propertiesNames();
		while(elements.hasMoreElements()) {
			String element = (String)elements.nextElement();
			if(element.endsWith(".url")) {
				String poolName = element.substring(0, element.lastIndexOf("."));
				String url = PropertiesMgr.getProperty(poolName + ".url");
				if(url == null) {
					log.error("无法连接到数据库" + poolName + "请检查配置文件连接字符串");
					continue;
				}
				String user = PropertiesMgr.getProperty(poolName + ".user");
				String pwd = PropertiesMgr.getProperty(poolName + ".password");
				String str_max = PropertiesMgr.getProperty(poolName + ".maxconn", "0");
				int maxConn = 0;
				try{
					maxConn = Integer.parseInt(str_max);
				}catch(NumberFormatException e) {
					log.error("数据库" + poolName + "最大连接数设置错误,默认设为20");
					maxConn = 20;
				}				
				DBConnectionPool pool = new DBConnectionPool(maxConn,url,poolName,user,pwd);
				pools.put(poolName, pool);
				log.info("成功创建数据库连接池" + poolName);
			}
		}
	}

	/**
	 * 获得单例
	 * @return DBConnectionManager单例
	 */
	public synchronized static DBConnectionManager getInstance() {
		if(dbm == null) {
			dbm = new DBConnectionManager();
		}
		clients++;
		return dbm;
	}

	/**
	 * 从指定连接池中获取可用连接,无等待
	 * @param poolName	要获取连接的连接池名称
	 * @return	连接池中的一个可用连接或null
	 */
	public Connection getConnection(String poolName) {
		DBConnectionPool pool = (DBConnectionPool)pools.get(poolName);
		return pool.getConnection();
	}
	
	/**
	 * 从指定的连接池获取可用连接,有等待超时
	 * @param poolName	要获取连接的连接池名称
	 * @param timeout	获取可用连接的超时时间,单位为秒
	 * @return			连接池中的一个可用连接或null
	 */
	public Connection getConnection(String poolName,long timeout) {
		DBConnectionPool  pool = (DBConnectionPool)pools.get(poolName);
		return pool.getConnection(timeout);
	}
	
	/**
	 * 回收指定连接池的连接
	 * @param poolName	连接池名称
	 * @param conn		要回收的连接
	 */
	public void freeConnection(String poolName,Connection conn) {
		DBConnectionPool pool = (DBConnectionPool)pools.get(poolName);
		if(pool != null) {
			pool.freeConnection(conn);
		}
		log.error("找不到连接池,无法回收,请检查参数");
	}
	
	/**
	 * 关闭所有连接,撤销驱动器的注册
	 */
	public synchronized void release() {
		//所有客户连接都关闭时才真正关闭连接撤销注册
		if(clients-- != 0) {
			return;
		}
		for(Map.Entry<String,DBConnectionPool> poolEntry:pools.entrySet()) {
			DBConnectionPool pool = poolEntry.getValue();
			pool.releaseAll();
		}
		log.info("已经关闭所有连接");
		for(Driver driver:drivers) {
			try {
				DriverManager.deregisterDriver(driver);
				log.info("撤销JDBC驱动器" + driver.getClass().getName() + "的注册");
			} catch (SQLException e) {
				log.error("撤销JDBC驱动器" + driver.getClass().getName() + "的注册异常");
			}
		}
		log.info("驱动器撤销完成");
	}
	
	/**
	 * 此内部类定义了一个连接池.
	 * 它能够获取数据库连接,直到预定的最 大连接数为止
	 * 在返回连接给客户程序之前,它能够验证连接的有效性
	 * @author shijin
	 */
	private class DBConnectionPool {
		private int activeNum = 0;
		private int maxConn = 0;
		private String url = null;
		private String poolName = null;
		private String user = null;
		private String pwd = null;
		private List<Connection> freeConnections = new ArrayList<Connection>();
		
		/**
		 * 
		 * @param maxConn	设定的连接池允许的最大连接数
		 * @param url		数据库连接url
		 * @param poolName	连接池名称
		 * @param user		数据库用户名,或null
		 * @param pwd		数据库用户密码,或null
		 */
		public DBConnectionPool(int maxConn, String url, String poolName,
				String user, String pwd) {
			super();
			this.maxConn = maxConn;
			this.url = url;
			this.poolName = poolName;
			this.user = user;
			this.pwd = pwd;
		}
		
		/**
		 * 获得一个可用连接,不保证任何情况都能返回一个连接(比如超过最大连接数的时候返回null)
		 * 1、若空闲连接池不为空,从空闲连接池取出一个连接后检查有效性,正常则返回,失效则重新获取连接
		 * 2、若空闲连接池为空且未超过最大连接数限制,新建一个连接并返回
		 * 3、空闲连接数为空且超过最大连接数限制,返回null
		 * @return	获得的可用连接
		 */
		public synchronized Connection getConnection() {
			Connection conn = null;
			//空闲连接池中有空闲连接,直接取
			if(freeConnections.size() > 0) {
				//从空闲连接池中取出一个连接
				conn = freeConnections.get(0);
				freeConnections.remove(0);
				//检测连接有效性
				try{
					if(conn.isClosed()) {
						//由于已经从空闲连接池取出,所以不使用无效连接其就无法重新进入
						//空闲连接池,意味着其已经被删除了,记入日志即可
						log.info("从连接池" + poolName + "中取出的连接已关闭,重新获取连接");
						//继续从连接池尝试获取连接
						conn = getConnection();
					}
				}catch(SQLException e) {
					log.info("从连接池" + poolName + "中取出的发生服务器访问错误,重新获取连接");
					conn = getConnection();
				}
			} else if(activeNum < maxConn) {
				conn = newConnection();
			} else {
				//未获得连接
			}
			if(conn != null) {
				activeNum++;
			}
			return conn;
		}
		
		/**
		 * 当无空闲连接而又未达到最大连接数限制时创建新的连接
		 * @return	新创建的连接
		 */
		private Connection newConnection() {
			Connection conn = null;
			try{
				if(user == null) {
					conn = DriverManager.getConnection(url);
				} else {
					conn = DriverManager.getConnection(url, user, pwd);
				}
				log.info("与数据库" + poolName + "创建一个新连接");
			}catch(SQLException e) {
				log.error("无法根据\"" + url + "\"与数据库" + poolName + "建立新连接");
			}
			return conn;
		}
		
		/**
		 * 获得一个可用连接,超过最大连接数时线程等待,直到有有连接释放时返回一个可用连接或者超时返回null
		 * @param timeout 等待连接的超时时间,单位为秒
		 * @return
		 */
		public synchronized Connection getConnection(long timeout) {
			Connection conn = null;
			long startTime = System.currentTimeMillis();
			while((conn = getConnection()) == null) {
				try{
					//被notify(),notifyALL()唤醒或者超时自动苏醒
					wait(timeout);
				}catch(InterruptedException e) {
					log.error("等待连接的线程被意外打断");
				}
				//若线程在超时前被唤醒,则不会返回null,继续循环尝试获取连接
				if(System.currentTimeMillis() - startTime > timeout*1000000)
					return null;
			}
			return conn;
		}
		
		/**
		 * 将释放的空闲连接加入空闲连接池,活跃连接数减一并激活等待连接的线程
		 * @param conn	释放的连接
		 */
		public synchronized void freeConnection(Connection conn) {
			freeConnections.add(conn);
			activeNum--;
			notifyAll();//通知正在由于达到最大连接数限制而wait的线程获取连接
		}
		
		/**
		 * 关闭空闲连接池中的所有连接
		 */
		public synchronized void releaseAll() {
			for(Connection conn:freeConnections) {
				try{
					conn.close();
					log.info("关闭空闲连接池" + poolName + "中的一个连接");
				}catch(SQLException e) {
					log.error("关闭空闲连接池" + poolName + "中的连接失败");
				}
			}
			freeConnections.clear();
		}
	}
}

connectionpool.util.Logger

package connectionPool.util;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

/**
 * 日志文件创建维护类,单例模式
 * @author shijin
 *
 */
public class Logger {
	
	private static Logger logger= null;
	private PrintWriter log = null;
	private static int level = 0;
	private Class<?> c = null;
	private static final int DEBUGLEVEL = 1;
	private static final int INFOLEVEL = 2;
	private static final int ERRORLEVEL = 3;
	
	private Logger(Class<?> c) {
		String logFileName = PropertiesMgr.getProperty("logfile","DBlog.txt");
		String str_level = PropertiesMgr.getProperty("loglevel", "3");
		this.c = c;
		level = Integer.parseInt(str_level);
		try {
			log = new PrintWriter(new FileWriter(logFileName),true);
		} catch (IOException e) {
			System.err.println("无法打开日志文件" + logFileName);
			log = new PrintWriter(System.err);
		}
	}
	
	public synchronized static Logger getInstance(Class<?> c) {
		if(logger == null) {
			logger = new Logger(c);
		}
		return logger;
	}
	
	
	public void debug(String msg) {
		if(level > DEBUGLEVEL) {
			msg = "DEBUG:" + new Date() + "-" + msg;
			System.out.println(msg);
			log.println(msg);
		}
			
	}
	
	public void info(String msg) {
		if(level > INFOLEVEL) {
			msg = "INFO:" + new Date() + "-" + msg;
			System.out.println(msg);
			log.println(msg);
		}
			
	}
	
	public void error(String msg) {
		if(level > ERRORLEVEL) {
			msg = "ERROR:" + new Date() + "-" + c + "-" + msg;
			System.out.println(msg);
			log.println(msg);
		}
	}
}

connection.util.PropertiesMgr

package connectionPool.util;

import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Properties;
/**
 * 属性文件加载管理类,单例模式
 * @author shijin
 *
 */
public class PropertiesMgr {
	private static Properties pro = new Properties();
	private PropertiesMgr(){}
	static {
		try {
			pro.load(PropertiesMgr.class.getClassLoader().getResourceAsStream("config" + File.separator + "DB.properties"));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static String getProperty(String key) {
		return pro.getProperty(key);
	}
	
	public static String getProperty(String key,String defaultValue) {
		//找不到key用defaultValue,而不是说后面为空字符串采用defaultValue
		return pro.getProperty(key, defaultValue);
	}
	
	public static Enumeration<?> propertiesNames() {
		return pro.propertyNames();
	}
}

DB.properties

driver=com.mysql.jdbc.Driver

mysql.url=jdbc:mysql://127.0.0.1/caiwu?useUnicode=true&characterEncoding=gb2312

mysql.user=root

mysql.password=123

mysql.maxconn=1000

#\u65E5\u5FD7\u6587\u4EF6\u7684\u540D\u79F0
logfile=
#\u65E5\u5FD7\u7EA7\u522B
loglevel=

 

抱歉!评论已关闭.