网站首页 > java教程 正文
数据源连接池的原理及Tomcat中的应用
在Java Web开发过程中,会广泛使用到 数据源 。
我们基本的使用方式,是通过Spring使用类似如下的配置,来声明一个数据源:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="maxActive" value="100" />
<property name="maxIdle" value="20" />
<property name="validationQuery" value="SELECT 1 from dual" />
<property name="testOnBorrow" value="true" />
</bean>
在之后应用里对于数据库的操作,都基于这个数据源,但这个 数据源连接池 的创建、销毁、管理,对于用户都是近乎透明的,甚至数据库连接的获取,我们都看不到Connection
对象了。
这种方式是应用自身的数据库连接池,各个应用之间互相独立。
在类似于Tomcat这样的应用服务器内部,也有提供数据源的能力,这时的数据源,可以为多个应用提供服务。
这一点类似于以前写过关于Tomcat内部的Connector对于线程池的使用,可以各个Connector
独立使用线程池,也可以共用配置的 Executor
。( Tomcat的Connector组件 )
那么,在Tomcat中,怎么样配置和使用数据源呢?
先将对应要使用的数据库的驱动文件xx.jar放到TOMCAT_HOME/lib目录下。
编辑
文件,增加类似于下面的内容:
TOMCAT_HOME/conf/context.xml
<Resource name="jdbc/TestDB" auth="Container" type="javax.sql.DataSource"
maxTotal="100" maxIdle="30" maxWaitMillis="10000"
username="root" password="pwd" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/test"/>
需要提供数据源的应用内,使用JNDI的方式获取
Context initContext = new InitialContext();
Context envContext = (Context)initContext.lookup("java:/comp/env");
DataSource ds = (DataSource)envContext.lookup("jdbc/TestDB");
Connection conn = ds.getConnection();
愉快的开始使用数据库…
我们看,整个过程也并不比使用Spring等框架进行配置复杂,在应用内获取连接也很容易。多个应用都可以通过第3步的方式获取数据源,这使得同时提供多个应用共享数据源很容易。
这背后的是怎么实现的呢?
这个容器的连接池是怎么工作的呢,我们一起来看一看。
在根据 context.xml
中配置的Resouce初始化的时候,会调用具体DataSource对应的实现类,Tomcat内部默认使用的BasicDataSource,在类初始化的时候,会执行这样一行代码 DriverManager.getDrivers()
,其对应的内容如下,主要作用是使用 java.sql.DriverManager
实现的 Service Provider 机制,所有jar文件包含
META-INF/services/java.sql.Driver文件的,会被自动发现、加载和注册,不需要在需要获取连接的时候,再手动的加载和注册。
public static java.util.Enumeration<Driver> getDrivers() {
java.util.Vector<Driver> result = new java.util.Vector<>();
for(DriverInfo aDriver : registeredDrivers) {
if(isDriverAllowed(aDriver.driver, callerClass)) {
result.addElement(aDriver.driver);
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
return (result.elements());
}
之后DataSourceFactory会读取 Resouce 中指定的数据源的属性,创建数据源。
在我们的应用内 getConnection
的时候,使用 ConnectionFactory
创建Connection, 注意在创建Connection的时候,重点代码是这个样子:
public PooledObject<PoolableConnection> makeObject() throws Exception {
Connection conn = _connFactory.createConnection();
initializeConnection(conn);
PoolableConnection pc = new PoolableConnection(conn,_pool, connJmxName);
return new DefaultPooledObject<>(pc);
这里的 _pool
是GenericObjectPool,连接的获取是通过其进行的。
public Connection getConnection() throws SQLException {
C conn = _pool.borrowObject();
}
在整个pool中包含几个队列,其中比较关键的一个定义如下:
private final LinkedBlockingDeque<PooledObject<T>> idleObjects;
我们再看连接的关闭,
public void close() throws SQLException { if (getDelegateInternal() != null) { super.close(); super.setDelegate(null);
}
}
这里的关闭,并不会真的调用到Connection的close方法,我们通过上面的代码已经看到,Connection返回的时候,其实是Connection的Wrapper类。在close的时候,真实的会调用到下面的代码
// Normal close: underlying connection is still open, so we
// simply need to return this proxy to the pool
try {
_pool.returnObject(this);
} catch(IllegalStateException e) {}
所谓的 return
,是把连接放回到上面我们提到的idleObjects队列中。整个连接是放在一个LIFO的队列中,所以如果没有关闭或者超过最大空闲连接,就会加到队列中。而允许外的连接才会真实的销毁 destory
。
int maxIdleSave = getMaxIdle(); if (isClosed() || maxIdleSave > -1 && maxIdleSave <= idleObjects.size()) { try {
destroy(p);
} catch (Exception e) {
swallowException(e);
}
} else { if (getLifo()) {
idleObjects.addFirst(p); // 这里。
} else {
idleObjects.addLast(p);
} if (isClosed()) { // Pool closed while object was being added to idle objects.
// Make sure the returned object is destroyed rather than left
// in the idle object pool (which would effectively be a leak)
clear();
}
}
总结下:以上是Tomcat内部实现的DataSource部分关键代码。数据源我们有时候也称为 连接池 ,所谓 池 的概念,就是一组可以不断重用的资源,在使用完毕后,重新恢复状态,以备再次使用。
而
为了达到重用的效果,对于客户端的关闭操作,就不能做真实意义上的物理关闭,而是根据池的状态,以执行具体的入队重用,还是执行物理关闭
。无论连接池,还是线程池,池的原理大致都是这样的。
猜你喜欢
- 2025-05-09 连接池之HikariCP:HikariCP框架设计与功能使用分析(第一部分)
- 2025-05-09 SpringBoot数据库操作的应用(springboot的数据库配置文件)
- 2025-05-09 Java数据库3大隐形陷阱!你的应用为何越跑越慢(附调优代码)
- 2025-05-09 深度剖析HikariCP:Java程序员的数据库利器
- 2025-05-09 Java工程师必知的数据库优化(java数据库工具包)
- 2025-05-09 Java线程池的四种用法与使用场景(java线程池的作用及使用方法)
- 2025-05-09 MySQL系列1:MySQL体系架构(mysql架构设计)
- 2025-05-09 你应该这样去开发接口:Java多线程并行计算
- 2025-05-09 假如面试官让你来设计数据库中间件
- 2025-05-09 Java暗藏杀机!ThreadLocal3大致命坑,90%程序员中招附逃生指南
你 发表评论:
欢迎- 最近发表
-
- 连接池之HikariCP:HikariCP框架设计与功能使用分析(第一部分)
- SpringBoot数据库操作的应用(springboot的数据库配置文件)
- Java数据库3大隐形陷阱!你的应用为何越跑越慢(附调优代码)
- 深度剖析HikariCP:Java程序员的数据库利器
- Java工程师必知的数据库优化(java数据库工具包)
- Java线程池的四种用法与使用场景(java线程池的作用及使用方法)
- MySQL系列1:MySQL体系架构(mysql架构设计)
- 你应该这样去开发接口:Java多线程并行计算
- 假如面试官让你来设计数据库中间件
- Java暗藏杀机!ThreadLocal3大致命坑,90%程序员中招附逃生指南
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)