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=