Hibernate 性能之数据库连接池

1. 前言

从本节课程开始,和大家一起聊聊 Hibernate 中的性能问题,面对开发者,Hibernate 表现出卓越的数据库操作能力。

使用框架最大的优势就是带来操作的快捷、便利。同时,因为框架的封装性,其性能往往比原生开发要慢。所以了解、掌握 Hibernate 的性能调优方案是提升性能的不二法则。

了解其性能优化方案,编写最好的性能优化策略,对每一个开发者而言,都是一个必选题。通过本节课程的学习,你将了解到:

  • 什么是数据库连接池;
  • HIbernate 中如何使用数据库连接池。

2. 数据库连接池

面对大量的数据库操作请求,数据库连接池能很好地帮助 Hibernate 避开网络开销所产生的性能消耗。

什么是数据库连接池?

一般讲池子是用来养鱼的,但数据库连接池不是养鱼的,而是养了好多的 Connection 对象。

当应用程序需要一个连接对象时,便向连接池租用一个。用完后,再返回给连接池,这样连接池中的连接对象便可以反复使用,达到重用连接对象的目的。

图片描述

Connection 的功能本质是通过网络 API 完成进程和进程之间的远程连接,每一次连接的性能消耗都是很大的。

如果每一次需要时都重开一个连接,用完后便立马销毁,其代价是非常大的,如果使用连接池便可以减少这种性能消耗。

Hibernate 本身没有提供较佳的数据库连接池实现,其实也没有必要重新造轮子。因为有行业认可的、稳定可靠的第三方数据库连接池可用。
如:

  • DBCP;
  • C3P0;
  • Proxool。

几位都是久经沙场考验、绝对忠诚可靠的老同志。

因为 Hibernate 3.0 后的版本不再支持 DBCP 数据库连接池,DBPC 在此略过不提。但是,不能质疑 DBCP 在行业内的领导性。

本节课就和大家一起讲解在 Hibernate 中使用数据库连接池,让其 Hibernate 的起飞姿势更优雅。

2.1 C3P0

  1. 添加 C3P0 依赖包:

从官网下载的 Hibernate 框架包中已经包含所有的 C3P0 依赖包,可直接添加进去:

  • c3p0-0.9.1.jar;
  • hibernate-c3p0-4.2.0.Final.jar。
  1. Hibernate 的主配置文件中添加如下属性后,便能自动启动 C3P0 连接池的功能。
<property name="connection.url">jdbc:mysql://localhost:3306/myhibernate?useSSL=false</property>
<property name="connection.username">root</property>
<property name="connection.password">abc123</property>
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.provider_class">org.hibernate.service.jdbc.connections.internal.C3P0ConnectionProvider</property>
<property name="hibernate.c3p0.pool_size">5</property>
<property name="hibernate.c3p0.max_size">20</property>
<property name="hibernate.c3p0.min_size">5</property>
<property name="hibernate.c3p0.timeout">120</property>
<property name="hibernate.c3p0.max_statements">100</property>
<property name="hibernate.c3p0.idle_test_period">120</property>
<property name="hibernate.c3p0.testConnectionOnCheckout">true</property>

Hibernate 会把产生连接对象的重担交给 C3P0

连接池对象本身会有一些常用的属性,在后面我们再详细讲解。

2.2 Proxool

  1. 添加依赖包:
  • hibernate-proxool-4.2.0.Final.jar;
  • proxool-0.8.3.jar。

ProxoolHibernate 官方指定的数据库连接池,在 Hibernate 的框架包中就包含的有,很容易找到。

  1. Hibernate 的主配置文件中添加:
<property name="hibernate.proxool.pool_alias">ProxoolPool</property>
<property name="hibernate.proxool.xml">proxool.xml</property>
<property name="hibernate.connection.provider_class">org.hibernate.service.jdbc.connections.internal.ProxoolConnectionProvider</property>
<property name="hibernate.proxool.existing_pool">true</property>

proxool 的配置和 C3P0 的配置有点不一样,在主配置文件中告诉 Hibernate 使用 proxool ,但是与 proxool 相关的更多配置信息需要放到特别指定的 proxool.xml 文件中。

所以,需要单独创建一个名为 proxool.xml 的文件,保存在和主配置文件相同的目录下。其内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<something-else-entirely>
	<proxool>
		<alias>ProxoolPool</alias>
		<driver-url>jdbc:mysql://localhost:3306/myhibernate?useSSL=false</driver-url>
		<driver-class>com.mysql.jdbc.Driver</driver-class>
		<driver-properties>
			<property name="user" value="root" />
			<property name="password" value="abc123" />
		</driver-properties>
		<!--最大连接数(默认5个),超过了这个连接数,再有请求时,就排在队列中等候,最大的等待请求数由maximum-new-connections决定 -->
		<maximum-connection-count>100</maximum-connection-count>
		<!--最小连接数 -->
		<minimum-connection-count>10</minimum-connection-count>
		<!--proxool 自动侦察各个连接状态的时间间隔(毫秒),侦察到空闲的连接就马上回收,超时的销毁默认30秒 -->
		<house-keeping-sleep-time>90000</house-keeping-sleep-time>
		<!--没有空闲连接时,可以分配在队列中等候的最大请求数-->
		<maximum-new-connections>10</maximum-new-connections>
		<!--最少保持的空闲连接数(默认2个) -->
		<prototype-count>5</prototype-count>
		<!--在使用之前进行测试 -->
		<test-before-use>true</test-before-use>
		<!--用于保持连接的测试语句 -->
		<house-keeping-test-sql>select CURRENT_DATE</house-keeping-test-sql>
	</proxool>
</something-else-entirely>

Hibernate 4.x 的低版本中使用 proxool 时存在一个没有解决的 bug 。
会抛出 “The url cannot be null” 异常,建议使用 proxool 时,切换高版本 Hibernate

当然,除了使用前面介绍的 c3p0proxoolHiberante 还可以通过 JNDI 的方式连接到特定服务器中提供的数据库连接池,有兴趣的话,可以自己研究研究。

3. 数据库连接池的实现原理

在程序的世界,有一个缓存概念,数据库连接池也可以看成是一个缓存池。

企业级的数据库连接池除了要考虑其复用以外,还要考虑并发、性能等诸多因素。实现一个完备的、被行业认定的数据库连接池并不是一件简单轻松的事情。

但如果只是讨论数据库连接池的基本原理,了解其实现过程,倒也不难。

数据库连接池主要解决以下 2 个方面的问题:

  • 不要影响或改变用户使用 connection 连接对象的标准流程。如创建、关闭等正常操作,但是用户在创建或需要连接对象时,不是直接创建,而是从池子里面寻找一个可用的连接对象;
  • 用户使用完连接对象后,在关闭连接对象时,不是真正关闭,而是返回给连接池。

对于连接池本身,需要考虑的问题有:

  • 一个应用程序中,一般只需要一个连接池对象,并保证在整个应用程序中都能访问。所以,连接池对象本身是基于单例设计模式;
  • 实现数据库连接池时,都会有一些基本的参数设置:
public class ConnectionPool implements DataSource {
	// 最大连接数,一般设置为0表示没有限制
	private int maxActive;
	// 最大空闲连接数 ,设 0 表示没有限制
	private int maxIdle;
	//连接池中最小空闲连接数
	private int minIdle;
	// 初始化连接数目
	private int initialSize;
	// 新的请求等待时间
	private int maxWait;
	//从没有正确关闭连接的程序中恢复数据库的连接
	private boolean removeAbandoned;
	// 活动连接的最大空闲时间,单位为秒
	private int removeAbandonedTimeout;
	// 连接池中连接可空闲的时间,单位为毫秒 针对连接池中的连接对象
	private int minEvictableIdleTimeMillis;
	private int timeBetweenEvictionRunsMillis;
}

数据库连接池中有一个比较重要的方法,为用户提供连接对象。此方法会使用代理设计模式,为用户提供一个代理对象,既不影响用户的正常使用,又能在用户使用期间进行代码注入。

如下面代码所示:

@Override
	public Connection getConnection() throws SQLException {
		return null;
	}

探讨数据库连接池的实现是一个高级的问题,即使是编写一个比较简单的连接池对象也将涉及到单例设计模式和代理设计模式,也需要创建动态代理对象的相关知识。

本节课还是偏向于从应用层面讲解 Hibernate 中如何使用数据库连接池,更多与连接池有关的深层次内容,有兴趣者可查阅相关资料。

4. 小结

结束往往意味着是一个新的开始。

本节课程和大家讲解了数据库连接池相关的概念,但是,数据库连接池的核心理论知识触及的还远远不够。

本课程通过讲解 Hibernate 使用数据库连接池, 就当是抛砖引玉,这里起一个头,大家有兴趣可以自己研究、探讨。