天天看點

初識資料庫連接配接池開源架構Druid

Druid是阿裡巴巴的一個資料庫連接配接池開源架構,準确來說它不僅僅包括資料庫連接配接池這麼簡單,它還提供強大的監控和擴充功能。本文僅僅是在不采用Spring架構對Druid的窺探,采用目前最新版本druid1.0.26 github位址:https://github.com/alibaba/druid。

在開始之前還是再說說為什麼不配套使用Spring來使用Druid連接配接池,原因其實很簡單,在Spring架構的配置檔案中僅僅一個配置datasource就可以使用Druid了。那到底配置這個datasource資料源時Spring到底對它做了什麼呢?它到底是怎麼來實作這個datasource資料源的呢?如果不知其二隻知其一,那才真是隻是個搬磚的。

下面我們正式開始吧,首先還是一覽工程包結構。

初識資料庫連接配接池開源架構Druid
同樣有兩個jar需要引入,一是druid,二是mysql-connector。
初識資料庫連接配接池開源架構Druid
我們首先實作util包裡的DBPoolConnection類,這個類用來建立資料庫連接配接池單例以及傳回一個資料庫連接配接。為什麼資料庫連接配接池需要單例呢?原因其實很簡單,我們可以想象在一個web應用中,同時可能會存在多個請求如果為每一個請求都建立一個資料庫連接配接池,那還有什麼意義呢?應該是不論有多少個并發請求,都應該隻存在一個資料庫連接配接池,在這個資料庫連接配接池中為每個請求建立一個資料庫連接配接。

1 package util;
 2 
 3 import java.io.File;
 4 import java.io.FileInputStream;
 5 import java.io.InputStream;
 6 import java.sql.SQLException;
 7 import java.util.Properties;
 8 
 9 import com.alibaba.druid.pool.DruidDataSource;
10 import com.alibaba.druid.pool.DruidDataSourceFactory;
11 import com.alibaba.druid.pool.DruidPooledConnection;
12 
13 /**
14  * 要實作單例模式,保證全局隻有一個資料庫連接配接池
15  * @author ylf
16  *
17  * 2016年10月21日
18  */
19 public class DBPoolConnection {
20     private static DBPoolConnection dbPoolConnection = null;
21     private static DruidDataSource druidDataSource = null;
22     
23     static {
24         Properties properties = loadPropertiesFile("db_server.properties");
25         try {
26             druidDataSource = (DruidDataSource)DruidDataSourceFactory.createDataSource(properties);    //DruidDataSrouce工廠模式
27         } catch (Exception e) {
28             e.printStackTrace();
29         }
30     }
31     
32     /**
33      * 資料庫連接配接池單例
34      * @return
35      */
36     public static synchronized DBPoolConnection getInstance(){
37         if (null == dbPoolConnection){
38             dbPoolConnection = new DBPoolConnection();
39         }
40         return dbPoolConnection;
41     }
42 
43     /**
44      * 傳回druid資料庫連接配接
45      * @return
46      * @throws SQLException
47      */
48     public DruidPooledConnection getConnection() throws SQLException{
49         return druidDataSource.getConnection();
50     }
51     /**
52      * @param string 配置檔案名
53      * @return Properties對象
54      */
55     private static Properties loadPropertiesFile(String fullFile) {
56         String webRootPath = null;
57         if (null == fullFile || fullFile.equals("")){
58             throw new IllegalArgumentException("Properties file path can not be null" + fullFile);
59         }
60         webRootPath = DBPoolConnection.class.getClassLoader().getResource("").getPath();
61         webRootPath = new File(webRootPath).getParent();
62         InputStream inputStream = null;
63         Properties p =null;
64         try {
65             inputStream = new FileInputStream(new File(webRootPath + File.separator + fullFile));
66             p = new Properties();
67             p.load(inputStream);
68         } catch (Exception e) {
69             e.printStackTrace();
70         } finally {
71             try {
72                 if (null != inputStream){
73                     inputStream.close();
74                 }
75             } catch (Exception e) {
76                 e.printStackTrace();
77             }
78         }
79         
80         return p;
81     }
82     
83 }      

第26行代碼執行個體化一個DruidDataSource時,我們可以通過Druid架構為我們提供的DruidDataSourceFactory建立出一個DruidDataSource執行個體,工廠模式給我們提供了大大的便利。

第36行getInstance方法為我們建立出一個資料庫連接配接池執行個體,這裡即用到了單例模式,在這個地方我們可以使用synchronized方法來對getInstance加鎖(懶加載)實作線程安全,同時我們也可以使用勤加載來實作線程安全即去掉synchronized關鍵字,删掉37-39行代碼,将第20行代碼修改為private static DBPoolConnection dbPoolConnection = new DBPoolConnection()。這兩種方式各有其優缺點,懶加載好處就是“用到才執行個體化”,缺點就是“synchronized關鍵字對方法加鎖的粒度稍稍有點大,采用同步的方式實作線程安全會帶來額外的開銷”,而勤加載的好處就是“不使用同步的方式實作線程安全,省去了同步機制帶來的額外開銷”,缺點即是“未用到也會執行個體化”。至于怎麼選擇,根據實際情況。這裡是之前對單例模式的兩篇博文,《單例模式》、《再說單例模式的線程安全問題》。

第55行代碼loadPropertiesFile方法是對properties配置檔案的加載。

我們在這個類所做的工作差不多就是在spring配置檔案中的那一句<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">。很簡單的一句話,這就是為什麼我不想使用Spring架構來配合使用Druid,因為這隻會造成隻知其一不知其二的結果。

我們接在來實作dao包中的DruidDao類,開始對資料進行持久化操作。由于我們沒有用到MyBatis等持久層架構,是以我們不得不使用JDBC來操作資料庫,雖然麻煩一點,但這是所有所有架構的基礎。

1 /**
 2  * 
 3  */
 4 package dao;
 5 
 6 import java.sql.PreparedStatement;
 7 import java.sql.SQLException;
 8 
 9 import com.alibaba.druid.pool.DruidPooledConnection;
10 
11 import util.DBPoolConnection;
12 
13 /**
14  * @author ylf
15  *
16  * 2016年10月21日
17  */
18 public class DruidDao {
19     
20     public void insert(String sql){
21         DBPoolConnection dbp = DBPoolConnection.getInstance();    //擷取資料連接配接池單例
22         DruidPooledConnection conn = null;
23         PreparedStatement ps = null;
24         try {
25             conn = dbp.getConnection();    //從資料庫連接配接池中擷取資料庫連接配接
26             ps = conn.prepareStatement(sql);
27             ps.executeUpdate();
28         } catch (SQLException e) {
29             e.printStackTrace();
30         } finally {
31             try {
32                 if (null != ps){
33                     ps.close();
34                 }
35                 if (null != conn){
36                     conn.close();
37                 }
38             } catch (Exception e) {
39                 e.printStackTrace();
40             }
41         }
42     }
43 }      

我們隻對資料做插入操作。下面我們測試一下,各個屬性的含義可參考:https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE%E5%B1%9E%E6%80%A7%E5%88%97%E8%A1%A8

1 /**
 2  * 
 3  */
 4 package test;
 5 
 6 import dao.DruidDao;
 7 
 8 /**
 9  * @author ylf
10  *
11  * 2016年10月21日
12  */
13 public class Client {
14 
15     /**
16      * @param args
17      */
18     public static void main(String[] args) {
19         DruidDao druidDao = new DruidDao();
20         String sql = "insert into test (name) values(\"keven\")";
21         druidDao.insert(sql);
22     }
23 
24 }      

檢視資料庫插入成功。

另外db_server.properties資料庫的配置檔案如下:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/DruidTest
username=root
password=0000
filters=stat
initialSize=2
maxActive=300
maxWait=60000
timeBetweenEvictionRunsMillis=60000
minEvictableIdleTimeMillis=300000
validationQuery=SELECT 1
testWhileIdle=true
testOnBorrow=false
testOnReturn=false
poolPreparedStatements=false
maxPoolPreparedStatementPerConnectionSize=200      

不積跬步,無以至千裡;不積小流,無以成江海。