java中数据库连接池_Java中的数据库连接池

本文译自Baeldung的博客

概述

连接池是一种众所周知的数据库访问模式,主要目的是减少创建数据库连接和读/写数据库操作的开销。

简单来说,连接池本质上就是数据库连接缓存的一种实现方式,可以通过对其进行配置来满足特定的需求。

本文中,我们会简要介绍一些流行的连接池框架,之后也会讨论如何从零开始实现一个连接池。

为何使用连接池

关于这个问题,只要我们分析一下典型的数据库连接的生命周期中所涉及的步骤,就会明白为什么:

使用数据库驱动建立一个到数据库连接;

建立 TCP socket用于读/写数据;

通过socket来读写数据;

关闭连接;

关闭socket;

很显然,数据库连接是非常昂贵的操作,因此在每个可能的应用场景中都要尽量将数据库连接操作降到最低。

这也就是数据库连接池发挥作用的地方。

只需要简单地实现一个数据库连接容器,使我们可以复用一些已存在的数据库连接,我们就可以有效地节省大量昂贵的数据库连接操作消耗的时间成本,从而提高数据库驱动应用程序的整体性能。

JDBC连接池框架

从实用角度来看,考虑到目前已有很多企业级连接池框架,从头开始实行连接池是没有意义的。但是从学习角度,也就是本文的角度来看,并不是无意义的。

即便如此,在开始学习如何实现基本的连接池之前,让我们首先了解几个流行的连接池框架。

Apache Commons DBCP

我们首先看一下Apache Commons DBCP组件,这是一个功能齐全的连接池JDBC框架:

public class DBCPDataSource{

private static BasicDataSource ds = new BasicDataSource();

static {

ds.setUrl("jdbc:h2:mem:test");

ds.setUsername("user");

ds.setPassword("password");

ds.setMinIdle(5);

ds.setMaxIdle(10);

ds.setMaxOpenPreparedStatements(100);

}

public static Connection getConnection() throws SQLException{

return ds.getConnection();

}

private DBCPDataSource(){ }

}

复制代码

在这个例子中,我们使用带有静态块的包装器类可以很容易地配置DBCP的属性。使用DBCPDateSource类获取池化连接的方式如下:

Connection con = DBCPDataSource.getConnection();

复制代码

C3P0

接着介绍的是C3P0,由Steve Waldman开发,是一个强大的JDBC4连接和语句池框架。

public class C3poDataSource{

private static ComboPooledDataSource cpds = new ComboPooledDataSource();

static {

try {

cpds.setDriverClass("org.h2.Driver");

cpds.setJdbcUrl("jdbc:h2:mem:test");

cpds.setUser("user");

cpds.setPassword("password");

} catch (PropertyVetoException e) {

// handle the exception

}

}

public static Connection getConnection() throws SQLException{

return cpds.getConnection();

}

private C3poDataSource(){}

}

复制代码

通过C3PoDataSource类获取池化连接的方式与前面类似:

Connection con = C3poDataSource.getConnection();

复制代码

HikariCP

最后来看一下HikariCP,一个由Breet Wooldridge开发的快速JDBC连接池框架。我们会在后续的文章中详细介绍HikariCP的配置和使用方式。

public class HikariCPDataSource{

private static HikariConfig config = new HikariConfig();

private static HikariDataSource ds;

static {

config.setJdbcUrl("jdbc:h2:mem:test");

config.setUsername("user");

config.setPassword("password");

config.addDataSourceProperty("cachePrepStmts", "true");

config.addDataSourceProperty("prepStmtCacheSize", "250");

config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

ds = new HikariDataSource(config);

}

public static Connection getConnection() throws SQLException{

return ds.getConnection();

}

private HikariCPDataSource(){}

}

复制代码

通过HikariCPDataSource类获取池化连接的方式同样很简单:

Connection con = HikariCPDataSource.getConnection();

复制代码

连接池简单实现

为了更好地理解连接池的底层逻辑,我们来实现一个简单的连接池。

首先,我们基于单个接口做一个松耦合设计:

public interface ConnectionPool{

Connection getConnection();

boolean releaseConnection(Connection connection);

String getUrl();

String getUser();

String getPassword();

}

复制代码

ConnectionPool接口定义了一个基本连接池所需的公共API。

现在,我们通过实现该接口来提供一些基础功能,包括获取和释放池化连接:

public class BasicConnectionPool implements ConnectionPool{

private String url;

private String user;

private String password;

private List connectionPool;

private List usedConnections = new ArrayList<>();

private static int INITIAL_POOL_SIZE = 10;

public static BasicConnectionPool create(String url,

String user,

String password)

throws SQLException{

List pool = new ArrayList<>(INITIAL_POOL_SIZE);

for (int i = 0; i 

pool.add(createConnection(url, user, password));

}

return new BasicConnectionPool(url, user, password, pool);

}

// standard constructors

@Override

public Connection getConnection(){

Connection connection = connectionPool.remove(connectionPool.size() - 1);

usedConnections.add(connection);

return connection;

}

@Override

public boolean releaseConnection(Connection connection){

connectionPool.add(connection);

return usedConnections.remove(connection);

}

private static Connection createConnection(String url,

String user,

String password)

throws SQLException{

return DriverManager.getConnection(url, user, password);

}

public int getSize(){

return connectionPool.size() + usedConnections.size();

}

// standard getters

}

复制代码

虽然很简单,但BasicConnectionPool类确实提供了我们期望从典型的连接池得到的基础功能。简单来说,该类基于一个可存储10个数据库连接的ArrayList来初始化连接池,从而使得这些连接可以被复用。

我们可以使用DriverManager类或Datasource实现来创建JDBC连接。从设计的角度来看,屏蔽数据库连接的创建过程更好,因此我们在create()静态工厂方法中选择了前者。

在本例中,我们把创建连接的方法放在了BasicConnectionPool类中,因为这个类是连接池接口的唯一实现类。但是在更复杂的设计中,可能存在多个ConnectionPool实现类,此时最好将该方法放在接口中,从而获得更灵活的设计和更强的内聚性。

需要强调的一点是,一旦创建了连接池,所有的连接都会从池中获取,因此不需要创建新的连接。此外,当一个连接被释放时,它实际上是被归还到池中,以便其他客户端可以重用它。

这里与底层数据库没有任何进一步的交互,例如对连接的close()方法的显式调用。

对于BasicConnectionPool的使用非常简单,我们可以写一个简单的单元测试,获取一个内存数据库H2的池化连接:

@Test

public whenCalledgetConnection_thenCorrect(){

ConnectionPool connectionPool = BasicConnectionPool

.create("jdbc:h2:mem:test", "user", "password");

assertTrue(connectionPool.getConnection().isValid(1));

}

复制代码

改进与重构

当然,我们还有很大的空间去改进或者扩展现在的线程池实现。

比如说,我们可以重构getConnection()方法,增加对最大连接池规模参数的支持。如果所有可用的连接都已经被使用,而且当前的连接数小于配置的最大值,该方法会创建新的连接:

@Override

public Connection getConnection() throws SQLException{

if (connectionPool.isEmpty()) {

if (usedConnections.size() 

connectionPool.add(createConnection(url, user, password));

} else {

throw new RuntimeException(

"Maximum pool size reached, no available connections!");

}

}

Connection connection = connectionPool.remove(connectionPool.size() - 1);

usedConnections.add(connection);

return connection;

}

复制代码

需要注意,该方法在这里抛出了SQLException,这意味着我们也需要修改接口中的方法签名。

此外,我们也可以增加方法来优雅地关闭连接池实例:

public void shutdown() throws SQLException{

usedConnections.forEach(this::releaseConnection);

for (Connection c : connectionPool) {

c.close();

}

connectionPool.clear();

}

复制代码

在企业级实现中,连接池需要提供很多额外的特性,比如跟踪当前使用中的连接的能力,对于预编译语句池的支持,等等。

为了保证简洁明了,我们省略了这些额外特性的实现,同时提供的也是非线程安全的实现。

在本文中,我们研究了什么是连接池,并学习了如何实现我们自己的连接池。

当然,我们需要在应用程序中添加连接池时,不必从头开发全新的连接池。这就是为什么我们首先对线程池做了简单的介绍,并展示了一些流行的连接池框架,以便于我们可以清楚地了解它们的使用方式,并选择最适合我们要求的框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值