手寫DAO框架(三)-數據庫鏈接

-------前篇:手寫DAO框架(二)-開發前的最後準備---------html

前言

上一篇主要是溫習了一下基礎知識,而後將整個項目按照模塊進行了劃分。由於是我的項目,一我的開發,本人採用了自底向上的開發。java

本篇會介紹鏈接層的代碼,包括三部分:pom配置、數據庫鏈接和數據庫鏈接池。mysql

pom配置

 1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 2     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 3     <modelVersion>4.0.0</modelVersion>
 4 
 5     <groupId>me.lovegao</groupId>
 6     <artifactId>gdao</artifactId>
 7     <version>0.0.2-SNAPSHOT</version>
 8     <packaging>jar</packaging>
 9 
10     <name>gdao</name>
11     <url>http://maven.apache.org</url>
12 
13     <properties>
14         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15         <junit.version>4.12</junit.version>
16     </properties>
17 
18     <dependencies>
19         <dependency>
20             <groupId>junit</groupId>
21             <artifactId>junit</artifactId>
22             <version>${junit.version}</version>
23             <scope>test</scope>
24         </dependency>
25         <dependency>
26             <groupId>org.slf4j</groupId>
27             <artifactId>slf4j-api</artifactId>
28             <version>1.7.12</version>
29         </dependency>
30         <dependency>
31             <groupId>com.alibaba</groupId>
32             <artifactId>fastjson</artifactId>
33             <version>1.2.31</version>
34         </dependency>
35         <dependency>
36             <groupId>org.apache.commons</groupId>
37             <artifactId>commons-lang3</artifactId>
38             <version>3.2.1</version>
39         </dependency>
40         <dependency>
41             <groupId>mysql</groupId>
42             <artifactId>mysql-connector-java</artifactId>
43             <version>5.1.31</version>
44         </dependency>
45     </dependencies>
46     <build>
47         <finalName>me_lovegao_gdao</finalName>
48         <plugins>
49             <plugin>
50                 <groupId>org.apache.maven.plugins</groupId>
51                 <artifactId>maven-compiler-plugin</artifactId>
52                 <version>3.7.0</version>
53                 <configuration>
54                     <target>1.8</target>
55                     <source>1.8</source>
56                 </configuration>
57             </plugin>
58         </plugins>
59     </build>
60 </project>

 maven定義了jdk的版本,省得導入IDE的時候變成1.5,又得修改。sql

數據庫鏈接

數據庫鏈接主要就是爲了獲取數據庫鏈接,這個模塊不關注鏈接的使用、關閉等,只提供獲取鏈接,其餘邏輯由調用方負責。數據庫

書上、網上都說,要面向接口編程。apache

原本我是定義了一個接口的,可是最終實現的時候沒有用上。回頭看這段代碼,感受用上接口以後,依賴關係有點複雜,因此就沒改了。編程

並且,這裏的獲取鏈接,定位的是一個工具類,因此直接寫了靜態方法。json

一、鏈接獲取類:ConnectionGetorapi

 1 package me.lovegao.gdao.connection;
 2 
 3 import java.sql.Connection;
 4 import java.sql.DriverManager;
 5 import java.util.Properties;
 6 
 7 import org.slf4j.Logger;
 8 import org.slf4j.LoggerFactory;
 9 
10 import me.lovegao.gdao.bean.SystemConstant;
11 
12 public class ConnectionGetor {
13     private final static Logger log = LoggerFactory.getLogger(ConnectionGetor.class);
14 
15     public static Connection createConnection(Properties properties) throws Exception {
16         Connection conn = null;
17         String driverName = properties.getProperty(SystemConstant.STR_DRIVER_NAME);
18         String url = properties.getProperty(SystemConstant.STR_CONNECTION_URL);
19         String userName = properties.getProperty(SystemConstant.STR_USER_NAME);
20         String passwd = properties.getProperty(SystemConstant.STR_USER_PASSWORD);
21         try {
22             Class.forName(driverName);
23             conn = DriverManager.getConnection(url, userName, passwd);
24         } catch (Exception e) {
25             log.error("createConnectionException", e);
26         }
27         return conn;
28     }
29 
30 }

爲了保證通用性,入參我選擇了Properties類型。這樣,配置既能夠從本地加載,也能夠從流加載,確保了靈活性。安全

爲了可以支持多數據源,因此這個方法裏沒有定義和數據庫鏈接相關的局部變量或者常量。

 

二、數據庫實例配置:Properties

說到了配置,我就把配置的字段先列一下。畢竟框架再好用,不教人怎麼配置就是扯。

##驅動名稱
driverName=com.mysql.jdbc.Driver
##鏈接url
connectionUrl=jdbc:mysql://localhost:3306/simple?useServerPrepStmts=false&rewriteBatchedStatements=true&connectTimeout=1000&useUnicode=true&characterEncoding=utf-8
##用戶名
userName=name
##用戶密碼
userPassword=123456
##初始化鏈接數
initConnectionNum=10
##最大鏈接數
maxConnectionNum=50
##最大查詢等待時間-秒
maxQueryTime=3

配置這塊就是這樣的。爲何定義了這幾個配置,在前兩篇文章裏也有涉及,這裏再也不贅述。

數據庫鏈接池

由於數據庫鏈接的建立是須要時間的,咱們能夠在項目初始化的時候先建立一批鏈接,在須要使用鏈接的時候能夠節省建立鏈接的等待時間,從而提升程序響應速度。並且,鏈接是能夠複用的,因此,項目中使用鏈接池是有意義的。

要面向接口編程,因此這裏先定義接口。

一、鏈接池接口:IConnectionPool

 1 package me.lovegao.gdao.connection;
 2 
 3 import java.sql.Connection;
 4 
 5 /**
 6  * 鏈接池
 7  * @author simple
 8  *
 9  */
10 public interface IConnectionPool {
11     /**
12      * 獲取鏈接
13      * @return
14      */
15     Connection getConnection() throws Exception;
16     /**
17      * 歸還鏈接
18      * @param conn
19      */
20     void returnConnection(Connection conn);
21     
22     /**
23      * 獲取查詢超時時間
24      * @return
25      */
26     int getQueryTimeoutSecond();
27 }

 從代碼能夠看到,鏈接池的主要功能包括:獲取鏈接,歸還鏈接。

「獲取查詢超時時間」這個接口是我後來加的,做爲一個框架,應該保證不由於一個查詢耗時超過正常區間仍然義無反顧的等待。只不過加在這裏,感受不太優雅,暫時也沒有想到更好的寫法,因此仍是暫時放在這裏吧。(有建議歡迎提出)

二、鏈接池的簡單實現:SimpleConnectionPool

 1 package me.lovegao.gdao.connection;
 2 
 3 import java.sql.Connection;
 4 import java.util.Map;
 5 import java.util.Properties;
 6 import java.util.Queue;
 7 import java.util.concurrent.ConcurrentHashMap;
 8 import java.util.concurrent.ConcurrentLinkedQueue;
 9 import java.util.concurrent.atomic.AtomicInteger;
10 
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13 
14 import me.lovegao.gdao.bean.SystemConstant;
15 
16 public class SimpleConnectionPool implements IConnectionPool {
17     private final static Logger log = LoggerFactory.getLogger(SimpleConnectionPool.class);
18     /**配置**/
19     private Properties properties;
20     /**保存鏈接的map**/
21     private final Map<Integer, Connection> CONNECTION_MAP_POOL = new ConcurrentHashMap();
22     /**鏈接池的鏈接索引**/
23     private final Queue<Integer> CONNECTION_KEY_POOL = new ConcurrentLinkedQueue();
24     /**鏈接池初始鏈接數量**/
25     private int POOL_INIT_NUM;
26     /**鏈接池最大鏈接數量**/
27     private int POOL_MAX_NUM;
28     /**已建立鏈接數量**/
29     private AtomicInteger POOL_CREATE_NUM = new AtomicInteger(0);
30     /**查詢超時時間-秒**/
31     private int QUERY_TIMEOUT_SECONDS;
32     
33     public SimpleConnectionPool(Properties properties) throws Exception {
34         this.properties = properties;
35         this.POOL_INIT_NUM = Integer.parseInt(properties.getProperty(SystemConstant.STR_INIT_CONNECTION_NUM));
36         this.POOL_MAX_NUM = Integer.parseInt(properties.getProperty(SystemConstant.STR_MAX_CONNECTION_NUM));
37         this.QUERY_TIMEOUT_SECONDS = Integer.parseInt(properties.getProperty(SystemConstant.STR_QUERY_TIME));
38         for(int i=0; i<POOL_INIT_NUM; i++) {
39             POOL_CREATE_NUM.incrementAndGet();
40             Connection conn = ConnectionGetor.createConnection(properties);
41             CONNECTION_MAP_POOL.put(conn.hashCode(), conn);
42             CONNECTION_KEY_POOL.add(conn.hashCode());
43         }
44     }
45     
46     @Override
47     public Connection getConnection() throws Exception {
48         Connection conn = null;
49         Integer connKey = CONNECTION_KEY_POOL.poll();
50         if(connKey == null) {
51             if(POOL_CREATE_NUM.intValue() < POOL_MAX_NUM) {
52                 int poolNum = POOL_CREATE_NUM.incrementAndGet();
53                 if(poolNum <= POOL_MAX_NUM) {
54                     conn = ConnectionGetor.createConnection(properties);
55                     CONNECTION_MAP_POOL.put(conn.hashCode(), conn);
56                 } else {
57                     POOL_CREATE_NUM.decrementAndGet();
58                 }
59             }
60         } else {
61             conn = CONNECTION_MAP_POOL.get(connKey);
62         }
63         //沒有獲取到鏈接
64         if(conn == null) {
65             throw new NullPointerException("鏈接池鏈接用完");
66         }
67         return conn;
68     }
69     
70     @Override
71     public void returnConnection(Connection conn) {
72         if(conn != null) {
73             try {
74                 if(conn.isClosed()) {
75                     CONNECTION_MAP_POOL.remove(conn.hashCode());
76                     POOL_CREATE_NUM.decrementAndGet();
77                 } else {
78                     CONNECTION_KEY_POOL.add(conn.hashCode());
79                 }
80             } catch (Exception e) {
81                 log.error("returnConnectionException", e);
82             }
83         }
84     }
85 
86     @Override
87     public int getQueryTimeoutSecond() {
88         return QUERY_TIMEOUT_SECONDS;
89     }
90 
91 }

邏輯簡介:初始化的時候,先建立指定數量的鏈接存起來。在獲取鏈接的時候,若是還有鏈接,就返回;若是沒有鏈接了,就判斷鏈接數是否到了最大上限,沒有到就建立並返回,到上限了就返回空。

其餘說明:

    爲了保證併發安全,這裏是經過POOL_CREATE_NUM變量來保證的。

    爲了支持多數據源,這裏一樣沒有靜態變量。

    爲了保存鏈接,這裏定義了兩個容器,分別是Map<Integer, Connection> CONNECTION_MAP_POOL和 Queue<Integer> CONNECTION_KEY_POOL。爲了併發安全,使用了ConcurrentHashMap和ConcurrentLinkedQueue來存儲。

        其中,隊列是用於取出鏈接和返回鏈接,這裏隊列存放的是鏈接對應的key。MAP是爲了保存對全部鏈接的引用,防止一些鏈接沒有歸還而系統殊不知道,同時也能夠知道鏈接的總數。也有一點考慮,是爲了之後能夠有單獨線程來定時判斷哪些線程應該關閉,主要是爲了之後擴展用。

 

最初,我想把鏈接池作成那種:獲取鏈接的時候,若是沒有鏈接,就等待一段時間(指定時間,即獲取鏈接等待時間)以後再試。查了一下對應的技術棧,綜合考慮了一下,感受有些不足之處,好比:若是鏈接用完了,可能出現大量線程等待的現象,反而會影響性能。

後來參考了一下其餘鏈接池的實現,最終作成目前的這樣:獲取鏈接的時候,沒有鏈接,看能不能建立,能建立就建立,不能建立就返回失敗。邏輯簡單,容易實現,快速錯誤。大概這就是fast-fail吧!

 

--本文內容到這裏就結束了,歡迎指導交流--

 

下篇內容:手寫DAO框架(四)-SQL執行

相關文章
相關標籤/搜索