几种常用数据库连接池的使用
一、应用程序直接获取数据库连接的缺点
用户每次请求都需要向数据库获得链接,而数据库创建链接通常需要消耗相对较大的资源,创建时间也比较长。假设网站一天10w访问量,数据库服务器就需要创建10w次连接,极大的浪费数据库资源,并且极易造成数据库服务器内存溢出,拓机。这里以javaweb为例 如下图所示:
二、使用数据库连接池优化程序性能
2.1、数据库连接池的基本概念
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现的尤为突出。对数据库连接的管理能显著的影响到整个应用程序的伸缩性和健壮性,影响到程序的性能指标,数据库连接池正式对这个问题提出来的。数据库连接池负责分配、管理和释放数据库,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。如下图所示:
数据库连接池在初始化的时候将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的,无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量,连接池的最大数据库连接数量限定了这个连接池限定了这个来连接池能占有的最大连接数,当应用程序向连接池请求的连接超过最大连接数量的时候,这些请求将被加入到等待队列中。
数据库连接池的最小连接数和最大连接数的设置要考虑到以下几点:
1.最小连接数:是连接池一直保持的数据库连接,所以如果应用程序对数据库连接的使用量不大,将会由大量的数据库连接资源被浪费。
2.最大连接数:是连接池能申请的最大连接数,如果数据库的连接请求超过次数,后面的数据库连接请求将被加入到等待队列中,这会影响以后的数据库操作。
3.如果最小连接数与最大连接数相差很大,那么最先连接请求将会获利,之后超过最小连接数量的连接请求等价于建立一个新的数据库连接,不过这些大于最小连接数的数据库连接在使用完不会马上被释放,他将被放到连接池中等待重复使用或者是空间超时后被释放。
2.2编写数据库连接池
编写数据库连接池需要实现java.sql.DataSource接口。DataSource接口中定义了两个重载的getConnection方法:
- Connection getConnection()
- Connection getConnection(String username,String password)
实现DataSource接口,并实现连接池的功能的步骤:
-
在DataSource构造函数中批量创建与数据库的连接,并把创建的连接加入到LinkedList对象中。
-
实现getConnection方法,让getConnection方法每次调用的时候,从LinkedList中取出一个Connection返回给用户。
-
当用户使用完Connection,调用Connection.close()方法的时候,Connection对象应该保证将自己返回到LinkedList中,而不要把conn返还给数据库,Connection对象保证自己返回到LinkedList中是此处编程的难点。
数据库连接池的核心代码:
使用动态代理的技术构建连接池中的connection
JdbcPool.java
大致思路:1.读取配置文件,将属性值取出
2.注册jdbc驱动
3.通过数据库连接数和驱动管理类获得相应的连接(getConnection) 因为 DataSource是接口 所以这个方法我们要手动进行实现
4.重点:实现getConnection方法
思考 :当外部由连接需求的时候,直接从ConnectList中拿出一个connect,就实现了这个方法,但是我们想想看,它用完了怎么怎么回收呢,确实,有close方法,但是这个方法是将这个连接还给数据库,而不是数据库连接池,这样会导致数据库连接池中的连接越来越少,这样可不行,但是我们也不能影响它的正常使用吧,在这种情况下,我们想要监控这个连接对象的动态,在它调用close方法的时候,我们将其再添加进ConnectList,这样来凝结吃中的连接不就没少了嘛,再java中对于监听一个对象的动态,最常用也最实用的便是动态代理,对于动态代理,其实我们可以把它想象成就是一个大盒子,里面装着一个真实对象的小盒子,这就再大盒子和小盒子之间形成了一个横切空隙,二真实做事的还是这个对象,代理只是个接活的,接到活就给内部的小盒子干,自然这个空隙可以用来监测真实对象的事务,也就是监听对象的方法,具体可以看看这个,动态代理: https://www.cnblogs.com/xdp-gacl/p/3971367.html
连接池类的实现
package cn.jdbc.util;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.*;
import java.util.LinkedList;
import java.util.Properties;
import java.util.logging.Logger;
/**
* @author wcc
* @date 2021/5/25 17:00
*/
public class JdbcPoolTest implements DataSource {
//存储数据库连接 相当于连接池
private static LinkedList<Connection> linkedList=new LinkedList<>();
static {
//静态代理块中加载dn.properties配置文件
InputStream in=JdbcPoolTest.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties=new Properties();
try {
//读取文件内容
properties.load(in);
String driver=properties.getProperty("driver");
String url=properties.getProperty("url");
String username=properties.getProperty("username");
String password=properties.getProperty("password");
int jdbcPoolInitSize=Integer.parseInt(properties.getProperty("jdbcPoolInitSize"));
//加载数据库驱动
Class.forName(driver);
//向连接池对象中存放连接对象
for (int i = 0; i <jdbcPoolInitSize ; i++) {
//获取连接
Connection connection= DriverManager.getConnection(url,username,password);
//将connection对象存放到linkedList对象中 此时的linkedList对象就是一个连接池对象
linkedList.add(connection);
}
System.out.println("数据库连接池的个数为初始化:"+linkedList.size());
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public Connection getConnection() throws SQLException {
//先判断数据库连接池的剩余连接数是否大于0
if (linkedList.size()>0){
//从linkedList对象中获取一个连接对象
final Connection connection=linkedList.removeFirst();
System.out.println("数据库连接池的大小为:"+linkedList.size());
//返回connection对象的代理 利用代理可以处理一些横切事件
return (Connection) Proxy.newProxyInstance(JdbcPoolTest.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//如果不是执行关闭操作 则通过反射执行相应方法
if(!method.getName().equals("close")){
return method.invoke(connection,args);
}else{
//否则 将connection归还给连接池
linkedList.add(connection);
System.out.println("归还连接"+connection);
System.out.println("连接池大小为:"+linkedList.size());
return null;
}
}
});
}else {
System.out.println("数据库正忙!");
}
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
测试连接池
package cn.jdbc.util;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* @author wcc
* @date 2021/5/25 17:18
*/
public class Test {
private static JdbcPoolTest jdbcPoolTest=new JdbcPoolTest();
public static Connection getConnection() throws Exception{
return jdbcPoolTest.getConnection();
}
//释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象
public static void release(Connection conn, Statement st , ResultSet rs) {
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
}
public static void main(String[] args) throws Exception {
Test.getConnection();
}
}
db.properties的配置信息(mysql8)
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybaits?characterEncoding=UTF-8&serverTimezone=UTC
username=root
password=120800
jdbcPoolInitSize=10
三、开源数据库连接池
现在很多web服务器(Weblogic WebSphere Tomcat)都提供了DataSource的实现,即连接池的实现。通常我们把DataSource的实现,按照其英文含义称之为数据源,数据源中都包含了数据库连接池的实现。
也有开源组织提供了数据源的独立实现:
- DBCP数据库连接池
- C3P0数据库连接池
在使用了数据库连接池之后,在项目的实际开发中就不需要编写连接数据库的代码了,直接从数据源中获得数据库的连接。
3.1、DBCP数据源
DBCP 是 Apache 软件基金组织下的开源连接池实现,要使用DBCP数据源,需要应用程序应在系统中增加如下两个 jar 文件:
Tomcat的连接池正是采用该连接池来实现的,该数据库连接池既可以与应用服务器整合使用,也可由应用程序独立使用。
3.2、在应用程序中加入dbcp连接池
相关jar的依赖导入
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.8.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.5</version>
</dependency>
<dependency>
<groupId>com.mchange</groupId>
<artifactId>mchange-commons-java</artifactId>
<version>0.2.20</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.6</version>
</dependency>
在resouces资源目录下加入dbcp的配置文件 db1.properties(这里以我的mysql8为例)、
注意 这里配置的并不全 但是我们可以通过BasicDataSourceFactory中的创建数据源的方法createDataSource(Properties)中看到,在这里面默认自动加载了很多配置信息,如果我们没有配置,会设置成默认信息。
DBCP实现类
package cn.jdbc.util;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author wcc
* @date 2021/5/25 18:03
*/
public class DbcpJdbc {
/**
* 在java中 编写数据库连接池需要实现javax.sql.DataSource接口 每一种数据库来凝结吃都是DataSource接口的实现
* Dbcp连接池对象就是javax.sql.DataSurce接口的一个具体实现
*/
private static DataSource dataSource=null;
static {
InputStream in=DbcpJdbc.class.getClassLoader().getResourceAsStream("db1.properties");
Properties properties=new Properties();
try {
//加载配置文件内容到集合中
properties.load(in);
//通过BasicDataSourceFactory工厂对象获得DataSource数据源 也就是驱动
dataSource= BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//释放的资源包括Connection数据库连接对象 负责执行sql命令的Statement对象 存储查询结果的Result对象
public static void release(Connection conn, PreparedStatement ps , ResultSet rs) {
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps!=null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
}
}
这里的测试跟上面的测试一样,直接调用类名.getConnection方法 因为我们把方法设置未静态的了,所以可以直接使用类名来调用。就不再演示测试了。
3.3、C3P0数据源
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0数据源在项目开发中使用得比较多。
我看了很多博客,都说的不是很清楚,如果想要有一个简单理解的话(不想浪费太多时间就理解一点的话),可以参照一下这个博客: https://www.jianshu.com/p/ad4c16e0b8ba
c3p0和dbcp的区别:
dbcp默认不自动回收空闲连接,需要手动开启
c3p0默认自动回收空闲连接功能
相关导入的jar包依赖在上面已经展示过了 后面两个即为c3p0数据源所使用的jar包
3.在类目录下加入c3p0的配置文件:c3p0-config.xml 即在项目根目录下读取文件
c3p0-config.xml的配置信息如下:
注意:配置文件中有两种内容相似的配置,一种是缺省配置,意思就是在没有指定配置文件名时,调用该配置,一种是命名配置,也就是在使用配置文件的时候,加上配置名即可。
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!--c3p0的缺省配置(默认配置)
如果在代码中ComboPooledDataSource使用空构造函数的时候即使用该配置-->
<default-config>
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mybaits?characterEncoding=UTF-8&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">120800</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</default-config>
<!--
C3P0的命名配置,
如果在代码中ComboPooledDataSource ds = new ComboPooledDataSource("MySQL");
这样写就表示使用的是name是MySQL的配置信息来创建数据源
-->
<named-config name="MySQL">
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/mybaits?characterEncoding=UTF-8&serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">120800</property>
<property name="acquireIncrement">5</property>
<property name="initialPoolSize">10</property>
<property name="minPoolSize">5</property>
<property name="maxPoolSize">20</property>
</named-config>
</c3p0-config>
jdbcUtils_C3P0:
大致思路:我们通过代码可以看到,c3p0将我们的代码量又缩减了许多
comboPooledDataSource=new ComboPooledDataSource("MySQL");我们也可以直接new 一个ComboPooledDataSource对象就可以获得数据源,所以这就是为什么之前需要将xml配置文件放在根目录下,MYSQL就是我们配置的名字。
package cn.jdbc.util;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* @author wcc
* @date 2021/5/25 20:17
*/
public class jdbcUtils_C3P0 {
private static ComboPooledDataSource comboPooledDataSource=null;
static {
//comboPooledDataSource=new ComboPooledDataSource("MySQL");
comboPooledDataSource=new ComboPooledDataSource();
}
public static Connection getConnection() throws SQLException {
//从数据源的连接池属性中获取对象
return comboPooledDataSource.getConnection();
}
//释放的资源包括Connection数据库连接对象 负责执行sql命令的Statement对象 存储查询结果的Result对象
public static void release(Connection conn, PreparedStatement ps , ResultSet rs) {
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps!=null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
}
}
测试C3P0数据源 --- jdbcUtils_C3P0Test
package cn.jdbc.util;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
/**
* @author wcc
* @date 2021/5/25 20:20
*/
public class jdbcUtils_C3P0Test {
public static void getDataSource(){
PreparedStatement preparedStatement=null;
ResultSet rs=null;
Connection connection=null;
try {
connection=jdbcUtils_C3P0.getConnection();
String sql="select * from account1";
preparedStatement=connection.prepareStatement(sql);
rs=preparedStatement.executeQuery();
while (rs.next()){
System.out.println("1111---");
}
}catch (Exception e){
e.printStackTrace();
}finally {
jdbcUtils_C3P0.release(connection,preparedStatement,rs);
}
}
public static void main(String[] args) {
jdbcUtils_C3P0Test.getDataSource();
}
}
还有一个阿里的druid(德鲁伊)连接池,下面直接看实现就好了
简单介绍一下Druid(德鲁伊)是阿里巴巴开发的号称为监控而生的数据库连接池,Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。
所需要导入的jar包依赖也在上面显示
JdbcConfigDruid类
package cn.jdbc.util;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;
/**
* @author wcc
* @date 2021/5/25 20:33
*/
public class JdbcConfigDruid {
private static DataSource dataSource=null;
static {
InputStream in=new JdbcConfigDruid().getClass().getClassLoader().getResourceAsStream("db2.properties");
Properties properties=new Properties();
try {
properties.load(in);
dataSource=DruidDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
//释放的资源包括Connection数据库连接对象 负责执行sql命令的Statement对象 存储查询结果的Result对象
public static void release(Connection conn, PreparedStatement ps , ResultSet rs) {
if (conn!=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (ps!=null) {
try {
ps.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (rs!=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
rs = null;
}
}
}
除了一个jndi的数据源没有说,常见的其他数据源应该都是已经说到了的
本文参考: https://blog.csdn.net/qq_36528311/article/details/87264571