论坛首页 Java企业应用论坛

一个类似于DBCP数据源(连接池)的实现(上)

浏览 3094 次
精华帖 (0) :: 良好帖 (1) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2010-04-30  

       这段时间,正在学Hibernate,在学习的过程中,回去复习了一下JDBC,收获颇大。其实,很早以前(大二),就接触JDBC了,只是当时觉得很简单,大概是那时对于软件开发的认识比较肤浅吧!总认为,能够通过别人提供的高级工具,例如:Struts、Hibernate、Spring开发出一个东西出来,就是很厉害了。所以,在那时候,感觉能够熟练使用JDBC的一些API,就算是精通JDBC了。

 

       现在才觉得,自己当时的想法是幼稚的,原谅我的年轻,我追求进步。

 

       今天,看了一下DBCP(Apache提供的通过连接池获取Connection的一个项目),然后用MyEclipse的debug功能,看了下使用DBCP的API获取Connection的执行能够过程。接着,花了几个小时的时间,自己写了一个数据源,与大家分享,倘若在设计上有什么不合理的地方,还请大家给鄙人指出。鄙人的进步需要你们。

 

       先谈下“为什么要使用连接池(数据源)”?

 

       我们知道,在我们使用JDBC操作数据库的时候,过程一般都是如此的:注册数据库驱动-->取得连接(Connection)-->取得Statement或者PreparedStatement-->(可能需要)取得ResultSet,而我们知道,在这一操作中,最耗时的操作是“取得Connection”这个过程。

 

       所以,我们在想?既然Connection创建的成本这么高。为什么不去复用它们呢!用完了就关闭,太浪费了。Connection就像一座桥,而PreparedStatement或Statement对象就像一辆车,ResultSet就像一匹货物。货物运完了,就把桥拆了,多浪费呀!所以,我们将这座桥(Connection对象)保留起来。用什么方式保留呢?一种比较容易实现的方式,当Connection对象不要用时,将Connection对象放在一个集合里,而不是关闭了。可以是ArrayList,可以是LinkedList(下面会强调两者的区别)。

 

 

       好,下面就是具体的实现过程。

 

  •        首先,需要有一个配置文件,配置文件是必须的。

 

<?xml version="1.0" encoding="UTF-8"?>
<datasource-configuraction>
	<!--连接设置  -->
	<driver-name>com.mysql.jdbc.Driver</driver-name>
	<url>jdbc:mysql://localhost:3306/jdbc</url>
	<username>root</username>
	<password>root</password>
	
	<!-- 初始化连接数 -->
	<inital-size>10</inital-size>
	<!-- 最大连接数 -->
	<max-active>20</max-active>
	<!-- 每个连接最大使用次数 -->
	<max-used>5</max-used>
</datasource-configuraction>

 

 

         该配置文件,配置了以下信息:

 

  •  数据库连接信息例如驱动,url,username,password

 

  •  初始化连接数:当数据源(下面的MyDataSource)实例化时,创建的Connection的个数

 

  •  最大连接数:我们知道,WEB环境,一般都是多线程环境的。由于Connection是非线程安全的,所以要确保每个线程拿到的都是不同的Connection。那假如有100个线程同时访问数据库,很显然,连接池里的Connection是不够用的,所以必须额外创建。而数据库所能承受的Connection个数是有限的,我们为了保证数据库的性能,不能让Connection无休止地创建下去。

 

  •  每个连接最大使用次数:见下文

 

  • 接着,有了配置文件,就要去读取了,我使用的是dom4j读取

 

package com.langgou.jdbc.datasource.util;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * 读取数据源配置信息
 * 
 * @author <a href="fumingfu1990@gmail.com">胡铭福</a>
 * 
 */
public final class DataSourceCfgReader {

	private static Document doc;
	private static DataSourceInfo dateSourceInfo;

	private DataSourceCfgReader() {

	}

	static {
		try {
			doc = new SAXReader().read(DataSourceCfgReader.class
					.getClassLoader().getResource("dataSourceCfg.xml"));

			setDataSourceInfo(doc);
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
//设置数据源配置信息
	private static void setDataSourceInfo(Document doc) {
		Element driverNameElt = (Element) doc
				.selectObject("/datasource-configuraction/driver-name");

		Element urlElt = (Element) doc
				.selectObject("/datasource-configuraction/url");

		Element userNameElt = (Element) doc
				.selectObject("/datasource-configuraction/username");

		Element passwordElt = (Element) doc
				.selectObject("/datasource-configuraction/password");

		Element initalSizeElt = (Element) doc
				.selectObject("/datasource-configuraction/inital-size");

		Element maxActiveElt = (Element) doc
				.selectObject("/datasource-configuraction/max-active");

		Element maxUsedElt = (Element) doc
				.selectObject("/datasource-configuraction/max-used");

		dateSourceInfo = new DataSourceInfo();
		dateSourceInfo.setDriverName(driverNameElt.getText());
		dateSourceInfo.setUrl(urlElt.getText());
		dateSourceInfo.setUserName(userNameElt.getText());
		dateSourceInfo.setPassword(passwordElt.getText());
		dateSourceInfo.setInitalSize(Integer.parseInt(initalSizeElt.getText()));
		dateSourceInfo.setMaxActive(Integer.parseInt(maxActiveElt.getText()));
		dateSourceInfo.setMaxUsed(Integer.parseInt(maxUsedElt.getText()));
	}
//取得数据源配置信息
	public static DataSourceInfo getDataSourceInfo() {
		
		return dateSourceInfo;
	}

}

 

 

  • 读取出来的信息,得用一个JavaBean来存放,这样会比较合理一点。因为类和类之间,一般都是通过JavaBean作数据通信的。

 

package com.langgou.jdbc.datasource.util;
/**
 * 保存数据源配置信息
 * @author <a href="fumingfu1990@gmail.com">胡铭福</a>
 *
 */
public class DataSourceInfo {

	/**
	 * 连接设置
	 */
	private String driverName;
	private String url;
	private String userName;
	private String password;
	/**
	 * 初始化的Connection个数
	 */
	private int initalSize;
	/**
	 * 已创建Connection的最大个数
	 */
	private int maxActive;
	/**
	 * Connection的最大使用次数
	 */
	private int maxUsed;
	public String getDriverName() {
		return driverName;
	}
	public void setDriverName(String driverName) {
		this.driverName = driverName;
	}
	public String getUrl() {
		return url;
	}
	public void setUrl(String url) {
		this.url = url;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	public int getInitalSize() {
		return initalSize;
	}
	public void setInitalSize(int initalSize) {
		this.initalSize = initalSize;
	}
	public int getMaxActive() {
		return maxActive;
	}
	public void setMaxActive(int maxActive) {
		this.maxActive = maxActive;
	}
	public int getMaxUsed() {
		return maxUsed;
	}
	public void setMaxUsed(int maxUsed) {
		this.maxUsed = maxUsed;
	}
}

 

  •  现在,配置信息读取出来了。接下来,就是重头戏了,也就是数据源的实现.(程序的注释已经写的很详细了,这里就不再详细解释了)

 

package com.langgou.jdbc.datasource;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;

import com.langgou.jdbc.datasource.util.DataSourceCfgReader;
import com.langgou.jdbc.datasource.util.DataSourceInfo;

public class MyDataSource {

	/**
	 * 数据源配置文件中的所有信息,当MyDateSource一被加载入虚拟机,就读取所有配置文件信息
	 */
	protected static DataSourceInfo dataSourceInfo = DataSourceCfgReader
			.getDataSourceInfo();

	private final static String PROP_DRIVERCLASSNAME = dataSourceInfo
			.getDriverName();
	private final static String PROP_URL = dataSourceInfo.getUrl();
	private final static String PROP_USERNAME = dataSourceInfo.getUserName();
	private final static String PROP_PASSWORD = dataSourceInfo.getPassword();
	/**
	 * 初始化的Connection个数
	 */
	private final static int PROP_INITIALSIZE = dataSourceInfo.getInitalSize();
	/**
	 * 最大的Connection个数
	 */
	private final static int PROP_MAXACTIVE = dataSourceInfo.getMaxActive();
	/**
	 * 每个Connection的最大使用次数
	 */
	private final static int PROP_MAXUSED = dataSourceInfo.getMaxUsed();

	/**
	 * 记录已创建的Connection个数
	 */
	protected int currentCount = 0;

	/**
	 * 连接池,将connectionPool设成protected的,只允许包内访问。外部只能通过调用getConnection()方法取得Connection
	 */
	protected LinkedList<Connection> connectionPool = new LinkedList<Connection>();

	/**
	 * 当MyDataSource被加载到虚拟机,就注册数据库驱动
	 */
	static {
		try {
			Class.forName(PROP_DRIVERCLASSNAME);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 当MyDataSource被实例化,就创建一批Connection,放入连接池里 构造方法
	 */
	public MyDataSource() {
		try {
			for (int i = 0; i < PROP_INITIALSIZE; i++) {
				this.connectionPool.addLast(this.createConnection());
				this.currentCount++;
			}
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * 
	 * @author <a href="fumingfu1990@gmail.com">胡铭福</a> 功能:从连接池里取得Connection
	 * @return
	 */
	public Connection getConnection() throws SQLException {
		// Connection属于非线程安全的,要确保每个线程拿到的都是不相同的Connection
		synchronized (connectionPool) {

			// 判断连接池里是否还有Connection
			if (this.connectionPool.size() > 0) {
				// 如果连接池里还有Connection,则直接从连接池中取出Connection
				return connectionPool.removeFirst();

			} else if (this.currentCount < PROP_MAXACTIVE) {
				// 判断当前的Connection个数是否超过Connection个数的上限
				// 因为数据库所能承受的Connection个数是有限的,不能让其无限创建下去
				this.currentCount++;
				try {
					// 创建一个新的Connection
					return this.createConnection();
				} catch (SQLException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			throw new SQLException("已经没有连接啦!");
		}
	}

	/**
	 * 
	 * @author <a href="fumingfu1990@gmail.com">胡铭福</a> 功能:释放Connection
	 * @param conn
	 */
	public void free(Connection conn) {
		// 为了达到Connection的复用,不能将Connection关闭,要放回连接池中
		this.connectionPool.add(conn);
	}
 /**
 *
 * @author <a href="fumingfu1990@gmail.com">胡铭福</a> 功能:创建Connection
 * @return
 * @throws SQLException
 * 问题:创建Connection的时候,采用ThreadLocal封装,是否有效?
 */
	private Connection createConnection() throws SQLException {
	// //采用ThreadLocal封装Connection,确保每个线程只对应一个Connection
	// ThreadLocal<Connection> connectionHolder = new
	// ThreadLocal<Connection>();
		Connection conn = null;
		try {
		// 取得与当前线程绑定的Connection
		// conn = connectionHolder.get();
		// 如果没有与当前线程绑定的Connection
		       // if(conn==null){
		      // 创建Connection
			conn = DriverManager.getConnection(PROP_URL, PROP_USERNAME,PROP_PASSWORD);
			// 将创建的Connection与当前线程绑定
			// connectionHolder.set(conn);
			// }
			} catch (SQLException e) {
			// TODO Auto-generated catch block
				e.printStackTrace();
		          }
		return conn;
	}



	// /**
	// *
	// * @author <a href="fumingfu1990@gmail.com">胡铭福</a>
	// * 功能:测试程序
	// * @param args
	// */
	// public static void main(String[] args) {
	// MyDataSource myDataSource = new MyDataSource();
	//		
	// for(int i=0;i<5;i++){
	// try {
	// System.out.println(myDataSource.getConnection());
	// } catch (SQLException e) {
	// // TODO Auto-generated catch block
	// e.printStackTrace();
	// }
	// }
	// }

}

 

 

存在的问题:由于当用户显式的调用Connection对象的close()方法时,默认会将Connection对象关闭,而不是返回连接池中,从而不能达到Connection的复用。所以要采取一种机制,改写掉Connection的close()方法默认的行为。

 

解决方案:

一般采用采用JDK动态代理,拦截所有Connection对象的close()方法,由代理类实现。将默认的关闭Connection对象的行为改为将Connection对象返回连接池,从而达到Connection对象的复用。而除了close()方法,外的其余方法,都由真实对象(realConnection对象)实现.

 

 

------------------------------------------------------------------这是一条分割线----------------------------------------------------------------------------------------------

 

由于时间关系,采用代理类包装Connection,这篇文章就不再介绍了。下篇文章,会详细介绍,并实现类似于DBCP包装Connection的方法。

 

由于是“处女贴”,写的不好的地方,请谅解。

论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics