数据库连接池很简单,项目没使用框架亦可使用

在前面我们讲解了JDBC的基本用法,包括Statement、PreparedStatement、CallableStatement接口的使用,存储过程、事务的使用方法。但是在我们每次操作数据库时,都会创建一个新的Connection,并且执行完毕后断开,Connection的新建、关闭操作十分的耗时并且耗资源,如果频繁的操作还会影响数据库服务器性能,过多的连接甚至会打崩数据库服务器。

资源分配图

​SUN说:要有’光’,于是数据库连接池就诞生了。感谢之前的大神,让现在的开发者可以站在他们的肩膀上,在我们的项目中可以很方便的使用数据库连接池来解决上面说到的难题。下面就让我们一起来看下数据库连接池的概念和具体用法。

1.数据库连接池的概念

​在JDBC编程中,每次创建和断开Connection对象都会消耗一定的时间和IO资源。这些问题在我们本地开发时可能还无法直观的体会到,但是如果项目的部署生产后,并发量达到一定程度时,问题就会暴露出来,而且是灾难性的。因此,数据库连接池就在千呼万唤中出现了。

数据库连接池(DataBase Connection Pool)负责分配、管理和释放数据库连接,它允许应用程序重复使用现有的数据库连接,而不是重新建立。这个思想类似于目前比较流行的"共享"模式,当你有需要时,去租(申请)一个资源,在你使用完毕归还(释放)后,这些资源并不会被"销毁",而是被系统回收起来等待下一个用户申请。

资源分配图

​上图为一个采用了数据库连接池操作数据库的简单示意图,从中我们可以看到当应用程序想操作数据库时,并不是向之前一样直接创建Connection,而是从数据库连接池中"申请"一个Connection;使用完毕后,连接池会将此Connection进行回收,并且可以将其交付给其他线程使用。并且连接池还可设置最大连接数,最小活跃数等配置,来减少数据库服务器的压力。

2.JDBC之DataSource接口

​为了支持对数据库连接的管理,JDBC在3.0版本时提供了DataSource(数据源)接口,其作为首选方法替代DriverManager类负责建立与数据库的连接。DataSource接口位于javax.sql包中,接下来我们简单了解下。

​通过DataSource对象访问的驱动不会将自身注册到DriverManager中,DataSource的实现类通常会在JNDI(Java Naming and Directory Interface)注册。

​我们来看下DataSource接口中提供的获取Connection的方法:

  • Connection getConnection()
  • Connection getConnection(String username, String password)

​ 其中getConnection()方法的实现如下(以MySQL驱动为例),可以看到,无参的getConnection方法是使用DataSource中配置好的默认的用户。两个方法本质上是相同的,getConnection(String username, String password)方法可以指定创建连接使用的用户名和密码。

//com.mysql.jdbc.jdbc2.optional.MysqlDataSource
/**
 * Creates a new connection using the already configured username and
 * password.
 * 
 * @return a connection to the database
 * 
 * @throws SQLException
 *             if an error occurs
 */
public java.sql.Connection getConnection() throws SQLException {
  return getConnection(this.user, this.password);
}

​MySQL驱动中实现的DataSource,并没有实现数据库连接的池化管理,而是使用直连的方式通过Driver来获取的。而数据库连接池的实现主要一些开源组织来提供的,下面我们一起来看下目前主流的数据库连接池。

3.数据库连接池简单对比

​现在使用比较多的数据库连接池DBCP、C3P0、BoneCP、Druid等,我们分别来简单的介绍下。

  1. C3p0: 支持JDBC2和JDBC3的标准规范,易于扩展。Hibernate、Spring框架支持。单线程,性能较差,适用于小型系统;
  2. DBCP:Apache组织下的开源连接池实现,也是Tomcat服务器使用的连接池组件, Jakarta commons-pool对象池机制。单线程,并发量低,性能不好,适用于小型系统;
  3. BoneCP:高效、免费、开源的Java数据库连接池实现库,速度最快,高度可扩展;
  4. Druid:Java语言中最好的数据库连接池,Druid还能够提供强大的监控和扩展功能,是一个可用于大数据实时查询和分析的高容错、高性能的开源分布式系统,尤其是当发生代码部署、机器故障以及其他产品系统遇到宕机等情况时,Druid仍能够保持100%正常运行。快速的交互式查询、高可用、可扩展。

几个数据库连接池主要功能对比:

Druid BoneCP DBCP C3P0
LRU
PSCache
PSCache-Oracle-Optimized
ExceptionSorter
更新维护

​LRU(最近最久未使用)是一个性能关键指标,如果数据库连接池遵从LRU,有助于数据库服务器优化,这是重要的指标。在测试中,Druid、DBCP是遵守LRU的。BoneCP、C3P0则不是。BoneCP在mock环境下性能可能好,但在真实环境中就不好了。

​PSCache是数据库连接池的关键指标,即PrepareStatement的查询结果是否缓存。在Oracle中,类似SELECT NAME FROM USER WHERE ID = ?这样的SQL,启用PSCache和不启用PSCache的性能可能是相差一个数量级的。Proxool是不支持PSCache的数据库连接池,如果你使用Oracle、SQL Server、DB2、Sybase这样支持游标的数据库,那你就完全不用考虑Proxool。

​Oracle 10系列的Driver,如果开启PSCache,会占用大量的内存,必须做特别的处理,启用内部的EnterImplicitCache等方法优化才能够减少内存的占用。这个功能只有DruidDataSource有。如果你使用的是Oracle Jdbc,你应该毫不犹豫采用DruidDataSource。

​ExceptionSorter是一个很重要的容错特性,如果一个连接产生了一个不可恢复的错误,必须立刻从连接池中去掉,否则会连续产生大量错误。这个特性,目前只有Druid实现。Druid的实现参考自JBoss数据库连接池。

​经过对比发现,Druid当之无愧是Java语言中最好的数据库连接池。不仅提供了高效的数据库连接池,还提供了强大的监控与扩展功能。

​因此,我们下面使用Druid来作为我们的数据库连接池,并给出示例配置。

4.Druid的使用

​Druid既然是Java语言里最好的数据库连接池,并且深受程序员圈的广泛认可,那我们自然也是一步上垒,直接动手撸代码。

​这里首先需要下载Druid的jar包(如果是maven项目,可以在pom文件中配置druid的依赖),我们选择1.1.21这个版本,下载地址:https://mvnrepository.com/artifact/com.alibaba/druid/1.1.21

资源分配图

​我们将jar导入到lib目录中,并build path。然后,在src目录下新建druid.properties配置文件,目录结构如下图所示:

资源分配图

​druid.properties中的属性如下,其中*是必须要配置的,其他属性可以选择性配置。

#驱动类所在位置
driverClassName=com.mysql.jdbc.Driver
#数据库url*
url=jdbc:mysql://localhost:3306/java_web?useSSL=false&characterEncoding=utf-8
#用户名*
username=root
#密码*
password=123456
#最大活跃连接数*
maxActive=20
#初始化时建立物理连接的个数
initialSize=1
#获取连接最大等待时间,单位ms
maxWait=60000
#最小空闲连接数
minIdle=5
#空闲连接保持最大存活时间,单位ms
minEvictableIdleTimeMillis=300000

​然后,我们在util包中新建DruidUtils,在其中创建数据源,并提供获取Connection的静态方法,其中的代码如下:

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import com.alibaba.druid.pool.DruidDataSourceFactory;

public class DruidUtils {

	private static DataSource dataSource;

	private DruidUtils() {
	}

	// 因为静态方法已经初始化了DataSource,这里直接返回即可
	public static DataSource getInstance() {
		return dataSource;
	}

	// 创建数据源,根据配置文件中的属性初始化数据库连接池
	static {
		Properties pro = new Properties();
		try {
			pro.load(DruidUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
			dataSource = DruidDataSourceFactory.createDataSource(pro);
		} catch (Exception e) {
			// 异常处理
			e.printStackTrace();
		}
	}

	/**
	 * 从数据库连接池中获取连接
	 * 
	 * @return
	 * @throws SQLException
	 */
	public static Connection getConnection() throws SQLException {
		return dataSource.getConnection();
	}
}

​配置好DruidUtil后,其使用和之前的JDBC操作数据库基本相同,除了获取连接的方式由从DriverManager变为从DataSource中获取。我们简单的写个测试代码:

public void selectById(int id) throws SQLException {
  Connection connection = null;
  Statement statement = null;
  ResultSet resultSet = null;
  try {
    // 获取数据库连接
    connection = DruidUtils.getConnection();
    // 创建Statement对象
    statement = connection.createStatement();
    String sql = "SELECT * FROM users WHERE id = " + id;
    // 获取查询结果集
    resultSet = statement.executeQuery(sql);
    User user = new User();
    while (resultSet.next()) {
      user.setId(resultSet.getInt("id"));
      user.setUserName(resultSet.getString("name"));
      user.setPassword(resultSet.getString("password"));
      user.setEmail(resultSet.getString("email"));
      user.setBirthday(resultSet.getDate("birthday"));
      System.out.println(user);
    }
  } catch (SQLException e) {
    e.printStackTrace();
    throw e;
  } finally {
    //此处的释放资源代码可参考前几篇博客
    JDBCUtil.release(resultSet, statement, connection);
  }
}

​我们执行测试代码selectById(2),运行结果如下图所示:

资源分配图

​在上面的的代码中,我们在finally代码块中进行了资源释放,这里大家可能会有些疑惑,就是获取的连接如果关闭掉的话,那下次使用不还是要新建连接了么?为了解答疑惑,这里我们还看下druid中的源码,

//druid中重写的getConnection方法,可以看到,其返回值的类型为DruidPooledConnection
@Override
public DruidPooledConnection getConnection() throws SQLException {
  return getConnection(maxWait);
}
资源分配图

​从上图中,我们可以看到DruidPooledConnection类是是继承了Connection接口的,而且还是支持数据库数据库连接池。并且在DruidPooledConnection类中,还重写了Connection接口中的close方法,源码如下:

@Override
public void close() throws SQLException {
  //如果已经释放,直接返回
  if (this.disable) {
    return;
  }
  //判断是否被重复close
  DruidConnectionHolder holder = this.holder;
  if (holder == null) {
    if (dupCloseLogEnable) {
      LOG.error("dup close");
    }
    return;
  }
  //回收此连接
  //...

  //设置回收成功标志位
  this.disable = true;
}

​因此,我们在使用数据库连接池时,还可以保持着使用JDBC编程的习惯,但是应用程序处理数据库操作并发的能力却悄然的大幅提升。

5.总结

​数据库连接池自其诞生以来,就广泛的应用于项目程序中,其对性能的提升是毋庸置疑的,并且,JDBC还新增了DataSource接口来支持数据库连接池,因此,我们需要掌握这把利器,并能使用好它。

​并且Spring、SpringBoot等框架对Druid的支持非常好,让我们可以快速的完成Druid配置,并且Druid还提供的监控的功能,可以满足你更多的需求。

参考阅读:

  1. JNDI
  2. Druid常见问题
  3. Druid Git Hub

​又到了分隔线以下,本文到此就结束了,本文内容全部都是由博主自己进行整理并结合自身的理解进行总结,如果有什么错误,还请批评指正。

​Java web这一专栏会是一个系列博客,喜欢的话可以持续关注,如果本文对你有所帮助,还请还请点赞、评论加关注。

​有任何疑问,可以评论区留言。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章