幾種常用數據庫連接池的使用
一、應用程序直接獲取數據庫連接的缺點
用戶每次請求都需要向數據庫獲得鏈接,而數據庫創建鏈接通常需要消耗相對較大的資源,創建時間也比較長。假設網站一天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