JDBC | 第七章: JDBC数据库连接池简单使用
概述
数据库连接池是负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是再重新建立一个。那么其中的运行机制又是怎样的呢?今天主要介绍一下数据库连接池原理和常用的连接池。
为什么要使用连接池
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。
数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库连接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。
连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
不使用连接池流程
下面以访问MySQL为例,执行一个SQL命令,如果不使用连接池,需要经过哪些流程。
不使用数据库连接池的步骤:
- TCP建立连接的三次握手
- MySQL认证的三次握手
- 真正的SQL执行
- MySQL的关闭
- TCP的四次握手关闭
可以看到,为了执行一条SQL,却多了非常多网络交互
优点:
实现简单
缺点:
- 网络IO较多
- 数据库的负载较高
- 响应时间较长及QPS较低
- 应用频繁的创建连接和关闭连接,导致临时对象较多,GC频繁
- 在关闭连接后,会出现大量TIME_WAIT 的TCP状态(在2个MSL之后关闭)
使用连接池流程
第一次访问的时候,需要建立连接。 但是之后的访问,均会复用之前创建的连接,直接执行SQL语句。
优点:
- 较少了网络开销
- 系统的性能会有一个实质的提升
- 没了麻烦的TIME_WAIT状态
数据库连接池的工作原理
连接池的工作原理主要由三部分组成,分别为
- 连接池的建立
- 连接池中连接的使用管理
- 连接池的关闭
第一、连接池的建立。
一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。
Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。
第二、连接池的管理。
连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。
该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
第三、连接池的关闭。
当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。
连接池需要注意的点
1、并发问题
为了使连接管理服务具有最大的通用性,必须考虑多线程环境,即并发问题。
这个问题相对比较好解决,因为各个语言自身提供了对并发管理的支持像java,c#等等,使用synchronized(java)lock(C#)关键字即可确保线程是同步的。
2、事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。
为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
3、连接池的分配与释放
连接池的分配与释放,对系统的性能有很大的影响。合理的分配与释放,可以提高连接的复用度,从而降低建立新连接的开销,同时还可以加快用户的访问速度。
对于连接的管理可使用一个List。即把已经创建的连接都放入List中去统一管理。每当用户请求一个连接时,系统检查这个List中有没有可以分配的连接。如果有就把那个最合适的连接分配给他,如果没有就抛出一个异常给用户。
4、连接池的配置与维护
连接池中到底应该放置多少连接,才能使系统的性能最佳?
系统可采取设置最小连接数(minConnection)和最大连接数(maxConnection)等参数来控制连接池中的连接。比方说,最小连接数是系统启动时连接池所创建的连接数。如果创建过多,则系统启动就慢,但创建后系统的响应速度会很快;如果创建过少,则系统启动的很快,响应起来却慢。这样,可以在开发时,设置较小的最小连接数,开发起来会快,而在系统实际使用时设置较大的,因为这样对访问客户来说速度会快些。最大连接数是连接池中允许连接的最大数目,具体设置多少,要看系统的访问量,可通过软件需求上得到。
如何确保连接池中的最小连接数呢?有动态和静态两种策略。动态即每隔一定时间就对连接池进行检测,如果发现连接数量小于最小连接数,则补充相应数量的新连接,以保证连接池的正常运转。静态是发现空闲连接不够时再去检查。
数据库连接池的使用
c3p0
package cn.soboys.kenx.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.io.FileInputStream;
import java.sql.Connection;
import java.util.Properties;
/*
* jdbc 通过数据连接池 c3p0 获取数据库连接
*/
public class C3p0DataSource {
//通过xml配置文件
public Connection getConnectionByC3p0PoolXml() {
//创建数据库连接池时,不传参数默认指default-config中参数,传入参数,则根据<name-config name="myc3p0">中的名字去找
try {
ComboPooledDataSource cpds = new ComboPooledDataSource();
//或者不使用默认的
ComboPooledDataSource cpds2 = new ComboPooledDataSource("myc3p0");
Connection conn = cpds.getConnection();
System.out.println(cpds.getProperties());//这里可以输出mysql的用户名和密码;
System.out.println(cpds.hashCode());//这里是输出连接的地址。
System.out.println("数据库连接成功");
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//通过代码配置
public Connection getConnectionByC3p0PoolCode() {
//创建数据库连接池时,不传参数默认指default-config中参数,传入参数,则根据<name-config name="myc3p0">中的名字去找
try {
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.cj.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql://localhost:3306/kenx_test?characterEncoding=utf-8");
cpds.setUser("root");
cpds.setPassword("root");
cpds.setInitialPoolSize(5);
cpds.setMaxPoolSize(10);
cpds.setCheckoutTimeout(3000);
Connection conn = cpds.getConnection();
System.out.println("数据库连接成功");
return conn;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//通过属性文件配置,默认c3p0不支持属性文件自动装配 需要我们自己手动设置属性
public Connection getConnectionByC3p0PoolProperties() {
//创建数据库连接池时,不传参数默认指default-config中参数,传入参数,则根据<name-config name="myc3p0">中的名字去找
try {
ComboPooledDataSource cpds = new ComboPooledDataSource();
//加载配置文件
Properties properties = new Properties();
properties.load(new FileInputStream("src/main/resources/c3p0.properties"));
String driver = properties.getProperty("driverClassName");
String url = properties.getProperty("jdbcUrl");
String username = properties.getProperty("username");
String password = properties.getProperty("password");
cpds.setDriverClass(driver);
cpds.setJdbcUrl(url);
cpds.setUser(username);
cpds.setPassword(password);
cpds.setInitialPoolSize(5);
cpds.setMaxPoolSize(10);
cpds.setCheckoutTimeout(3000);
Connection conn = cpds.getConnection();
System.out.println("数据库连接成功");
return conn;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
dbcp
package cn.soboys.kenx.datasource;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.util.Properties;
/*
* jdbc 通过数据连接池 DBCP 获取数据库连接
*/
public class DbcpDataSource {
//通过代码配置
public Connection getConnectionByDBCPPoolCode() {
try {
BasicDataSource bds = new BasicDataSource();
bds.setUrl("jdbc:mysql://localhost:3306/kenx_test?characterEncoding=utf-8");
bds.setDriverClassName("com.mysql.jdbc.Driver");
bds.setUsername("root");
bds.setPassword("root");
bds.setInitialSize(5);
Connection conn = bds.getConnection();
System.out.println("数据库连接成功");
return conn;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//通过属性文件配置,数据源工厂不需要手动配置单个属性
public Connection getConnectionByDBCPPoolProperties() {
Properties prop = new Properties();
try {
prop.load(new FileInputStream("src/main/resources/dbcp.properties"));
BasicDataSource bds = BasicDataSourceFactory.createDataSource(prop);
System.out.println("数据库连接成功");
return bds.getConnection();
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
durid
package cn.soboys.kenx.datasource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.sql.Connection;
import java.util.Properties;
/*
* jdbc 通过数据连接池 Druid 获取数据库连接
*/
public class DruidDatasource {
//通过属性文件配置,数据源工厂不需要手动配置单个属性
public Connection getConnectionByDuridPoolProperties() {
Properties prop = new Properties();
try {
prop.load(new FileInputStream("src/main/resources/durid.properties"));
// 获取连接池对象
DataSource dataSource = DruidDataSourceFactory.createDataSource(prop);
System.out.println("数据库连接成功");
return dataSource.getConnection();
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
//通过代码配置
public Connection getConnectionByDBCPPoolCode() {
try {
DruidDataSource bds=new DruidDataSource();
bds.setUrl("jdbc:mysql://localhost:3306/kenx_test?characterEncoding=utf-8");
bds.setDriverClassName("com.mysql.cj.jdbc.Driver");
bds.setUsername("root");
bds.setPassword("root");
bds.setInitialSize(5);
Connection conn = bds.getConnection();
System.out.println("数据库连接成功");
return conn;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
完整项目案例
点击这里 github