-------前篇:手寫DAO框架(二)-開發前的最後準備---------html
上一篇主要是溫習了一下基礎知識,而後將整個項目按照模塊進行了劃分。由於是我的項目,一我的開發,本人採用了自底向上的開發。java
本篇會介紹鏈接層的代碼,包括三部分:pom配置、數據庫鏈接和數據庫鏈接池。mysql
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執行