本文译自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();

}

复制代码

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

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

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

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

java中数据库连接池_Java中的数据库连接池相关推荐

  1. java池_java中的各种池

    常量池.线程池.连接池 一.常量池 1.什么是常量 是一种值,这个值本身 例如:八大基本数据类型 加字符串 不可变的变量,被final修饰的 例如 final int i=1; String str= ...

  2. java构造单例线程池_java中常见的六种线程池详解

    之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java 中常见的几种线程池,以及在jdk7 加入的 ForkJoin 新型线程池 首先我们列出Java 中的六种线程池如 ...

  3. java定义字符串常量_Java中的字符串常量池

    ava中字符串对象创建有两种形式,一种为字面量形式,如String str = "droid";,另一种就是使用new这种标准的构造对象的方法,如String str = new ...

  4. java变量存储位置_java 中变量存储位置的区别

    [原文] 1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量 ...

  5. java和equals区别_JAVA中==与equals的区别

    equals如果没有被重写的话,和==的作用是一样的,都是判断两个对象引用是否指向同一个地址.一般重写了equals()方法就表示比较它们"实际意义上相等",比较的是内容,而不是引 ...

  6. java final内存机制_Java中的内存处理机制和final、static、final static总结

    装载自:http://blog.csdn.net/wqthaha/article/details/20923579 Java程序运行在JVM上,可以把JVM理解成Java程序和操作系统之间的桥梁,JV ...

  7. Java字符串的重要性_java中的字符串相关知识整理

    字符串就是一系列的字符组合的串,如果写过C/C++的应该就了解,在字符串的操作上会有许多操作的函数与类,用于简化代码的开发.一方面是因为字符串在代码中会频繁用到,另一方面是因为字符串的操作非常麻烦. ...

  8. java的string类_Java中的String类笔记

    说明 源码 //jdk8 public final class String implements java.io.Serializable, Comparable, CharSequence { / ...

  9. java多线程 线程安全_Java中的线程安全

    java多线程 线程安全 Thread Safety in Java is a very important topic. Java provides multi-threaded environme ...

最新文章

  1. 成人高考计算机考试技巧,备战2015年成人高考:计算机基础考试经验分享
  2. IOS15一个工作空间创建多个项目
  3. 不吹牛逼,撸个注解有什么难的
  4. rocketmq存储消息mysql_RocketMQ消息消费以及进度管理解析
  5. 分布式消息队列 NSQ 和 Kafka 对比
  6. 使用Junit对Android应用进行单元测试
  7. ENVI5.3.1 去除影像四周的黑色背景
  8. 基于FPGA的任意字节数的串口发送(含源码工程)
  9. html用div排版类型table,DIV排版和Table排版的区别
  10. linux下如何删除垃圾文件
  11. Python小游戏——王校长的S8冠军奖杯【下】
  12. 2020计算机数电实验二
  13. 微软正式发布.NET 6:号称迄今为止最快
  14. 农行2021计算机专业面试题,【面经】2021 中国农业银行 笔试编程题
  15. Win64位系统安装Codewarrior 6.3
  16. 关于电脑网络浏览器没有网络,但是QQ和微信可以登录,解决浏览器网络问题
  17. geoservice地图openlayers图层+echart图表政区添加色块
  18. gradle强制刷新依赖
  19. 大航海时代4威力加强版 for mac
  20. SANGFOR WOC初级认证——加速技术详解

热门文章

  1. Python的__getattr__方法学习
  2. python 归并排序,合并有序数组,逆序对个数
  3. python实现素数筛选法_从零开始学Python系列-第6讲:循环结构
  4. word文档怎么限制编辑(禁止编辑、只读)?
  5. 中级软考 计算机指令执行过程(取指、分析、执行)计算机重叠流水线问题
  6. Python 科学计算库 Numpy (二) —— 索引及切片
  7. linux——sed 流编辑器
  8. Vupsen, Pupsen and 0 思维,凑系数
  9. Android m 自定义下拉菜单,Android实现动画效果的自定义下拉菜单功能
  10. SpringBoot笔记:SpringBoot2.3读取应用配置参数的几种方式