`
会爪哇的狼狗
  • 浏览: 17665 次
  • 性别: Icon_minigender_1
  • 来自: 珠海
社区版块
存档分类
最新评论

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

阅读更多

       这段时间,正在学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的方法。

 

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

分享到:
评论

相关推荐

    dbcp数据连接池配置

    DBCP(Database Connection Pool)是Apache的一个开源项目Commons DBCP,它提供了一个数据库连接池的实现,用于管理数据库连接。数据库连接池是应用程序管理数据库连接的一种方式,通过复用已建立的数据库连接,避免...

    java数据源连接池、dbcp连接和c3p0连接

    Java 数据源连接池是Java应用程序在处理数据库连接时的一个重要技术。它允许程序在需要时获取数据库连接,使用完毕后释放回池,而不是直接关闭,从而提高了应用的性能和资源利用效率。本压缩包文件主要涵盖了两个...

    数据库连接池dbcp几个jar包,commons-dbcp.jar、c3p0-0.9.1.2.jar等

    这里提到的几个jar包都是用于实现数据库连接池功能的库,分别是Apache的DBCP(Jakarta Commons DBCP)和C3P0。 1. **Apache Commons DBCP**: `commons-dbcp.jar` 是Apache Commons项目的一部分,提供了数据库连接...

    连接池 (jsp)

    在JSP和MVC应用中,我们通常使用像Apache DBCP、C3P0或HikariCP这样的连接池实现。这些库提供了初始化连接池、设置最大最小连接数、超时策略等功能。配置连接池通常涉及以下步骤: 1. 添加对应连接池的依赖到项目中...

    hibernate配置数据库连接池的三种方法

    这里,`connection.pool_size`用于设置连接池的大小,而`hibernate.connection.datasource`指定使用DBCP数据源。 3. **Apache DBCP2连接池配置** DBCP2是DBCP的升级版,解决了DBCP的一些已知问题并提供了更多功能...

    dbcp依赖jar包

    1. **连接池管理**:类似于DBCP,c3p0也负责创建、分配、回收和管理数据库连接。 2. **自动测试**:c3p0会在连接返回到连接池前进行自动测试,确保连接的可用性。 3. **性能优化**:c3p0引入了一些特有的性能优化...

    dbcp.rar_Commons-jdbc.zip jar_DBCP_commons jdbc_dbcp jar_dbcp.ja

    而"c3p0.gz"则可能是一个c3p0的压缩文件,c3p0是另一个常见的JDBC连接池实现,与Apache Commons DBCP类似,用于管理数据库连接。 总结一下,这个压缩包可能包含的是Apache Commons DBCP的组件和依赖,用于搭建和...

    Spring各种连接池的比较.doc

    DBCP提供了一个基本的连接池实现,但如描述中提到的,它存在一个问题:在强制关闭连接或数据库重启后,无法自动重新连接。为了解决这个问题,可以配置验证查询(validationQuery)和检查连接是否可用(testOnBorrow...

    Tomcat配置数据库连接池

    配置方法与局部数据源类似,但其影响范围更广,适用于服务器内的所有应用。 配置完成后,应用可以通过JNDI(Java Naming and Directory Interface)查找并使用数据源,例如在Spring框架中,可以使用`@Resource`注解...

    Spring配置多个数据源

    这通常可以通过使用`org.springframework.jdbc.datasource.DriverManagerDataSource`或更高级的如`HikariCP`、`Apache Commons DBCP`等连接池实现。例如,对于主数据库,我们可以这样配置: ```java @Bean(name =...

    Tomcat数据源配置

    【标题】:Tomcat数据源配置 ...总结,Tomcat数据源配置是一个涉及多方面因素的过程,包括版本差异、连接池实现选择以及最佳实践的应用。理解并掌握这些知识点,将有助于我们构建稳定、高效的Web应用环境。

    配置连接池的文件.rar

    在配置连接池时,可以在这个文件中定义数据源(DataSource)的配置,例如使用Apache DBCP或C3P0等连接池实现。这通常涉及 `&lt;resource-ref&gt;` 和 `&lt;context-param&gt;` 标签,指定连接池的类型、数据库URL、用户名、密码...

    java web 开发jdbc数据源jar包

    `commons-dbcp-1.4.jar`是Apache Commons Database Connection Pooling(DBCP)项目的一部分,它是一个基于Apache Commons Pool对象池实现的数据库连接池。数据库连接池在初始化时会创建一定数量的数据库连接,并在...

    java.lang.ClassNotFoundException: org.apache.commons.dbcp.BasicDataSource解决方案

    Apache Commons DBCP(Database Connection Pool)是一个开源的数据库连接池组件,它提供了数据源对象,方便管理和复用数据库连接,从而提高应用性能。 要解决这个问题,你需要确保你的Java项目正确地包含了Apache ...

    MySQL连接池代码及详细说明

    在Java开发中,常见的MySQL连接池实现有C3P0、DBCP、HikariCP、Druid等。以HikariCP为例,这是一个高性能的连接池,它的设计目标是提供低延迟和高吞吐量。下面将详细介绍HikariCP的配置和使用。 首先,我们需要在...

    spring下的各种连接池的比较算法及代码程序.doc

    DBCP是Apache Commons库中的一个数据源实现,它是Spring框架早期推荐的连接池。DBCP提供了一个基础的数据源实现,能够有效地管理和复用数据库连接。然而,DBCP存在一个问题,即在数据库重启或强制关闭连接后,它...

    java-jdbc-连接池大礼包

    综上所述,"java-jdbc-连接池大礼包"涵盖了JDBC的基本使用、连接池的概念及其实现,以及针对SQL Server 2000/2005、Oracle和MySQL等常见数据库的交互。掌握这些知识,开发者能够有效地管理和优化数据库连接,提升...

    spring切换数据源样例代码

    在Spring框架中,我们可以通过DataSource接口来配置数据源,通常使用Apache Commons DBCP或HikariCP等连接池实现。每个数据源都可以被定义为一个Bean,并通过@Configuration和@Bean注解进行配置。例如: ```java @...

Global site tag (gtag.js) - Google Analytics