0.學(xué)習(xí)原因
學(xué)習(xí)Spring時(shí)涉及到了這方面的知識倔既,就來學(xué)一下恕曲。
以前學(xué)過鹏氧,但是忘的差不多了渤涌。
學(xué)習(xí)博客:
http://blog.csdn.net/shuaihj/article/details/14223015
https://www.cnblogs.com/sunseine/p/5947448.html
http://happyqing.iteye.com/blog/2304131
1.連接池簡介
- 簡單來說:
就是一個(gè)創(chuàng)建和管理數(shù)據(jù)庫連接的緩沖池。類似緩存把还,已經(jīng)存好了多個(gè)連接实蓬,使用時(shí)直接取出使用茸俭,使用完畢再放回連接池,不用關(guān)閉連接安皱。 - 對于大多數(shù)應(yīng)用程序调鬓,當(dāng)它們正在處理通常需要數(shù)毫秒完成的事務(wù)時(shí),僅需要能夠訪問JDBC連接的 1 個(gè)線程酌伊。當(dāng)不處理事務(wù)時(shí)腾窝,這個(gè)連接就會閑置。相反居砖,連接池允許閑置的連接被其它需要的線程使用虹脯。
-
一般的JDBC連接的示意圖:
如果有多個(gè)人請求,則需要多個(gè)連接的開啟與關(guān)閉奏候。
-
使用連接池的操作:
連接可以共用循集,不用為每個(gè)請求額外創(chuàng)建一個(gè)JDBC連接。
- 常用的Java連接池有兩個(gè):DBCP和C3P0蔗草。
而DBCP又分為了dbcp與dbcp2咒彤。
2.連接池的簡單實(shí)現(xiàn)
1)原理與簡單實(shí)現(xiàn):
-
一個(gè)最簡單的實(shí)現(xiàn):
public class MyDataSource implements DataSource { //鏈表 --- 實(shí)現(xiàn)棧結(jié)構(gòu) private LinkedList<Connection> dataSources = new LinkedList<Connection>(); //初始化連接數(shù)量 public MyDataSource() { //一次性創(chuàng)建10個(gè)連接 for(int i = 0; i < 10; i++) { try { //1、裝載sqlserver驅(qū)動對象 DriverManager.registerDriver(new SQLServerDriver()); //2咒精、通過JDBC建立MySql數(shù)據(jù)庫連接 Connection con =DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/sqltest?useUnicode=true&characterEncoding=UTF-8", "root", "root"); //3镶柱、將連接加入連接池中 dataSources.add(con); } catch (Exception e) { e.printStackTrace(); } } } @Override public Connection getConnection() throws SQLException { //取出連接池中一個(gè)連接 finalConnection conn = dataSources.removeFirst(); // 刪除第一個(gè)連接返回 return conn; } //將連接放回連接池 public void releaseConnection(Connection conn) { dataSources.add(conn); } }
連接池大大提供了數(shù)據(jù)庫連接的利用率,減小了內(nèi)存吞吐的開銷模叙。
但是連接池還需要考慮許多問題奸例。
2)連接池還需要考慮的問題:
- 并發(fā)問題:
為了使連接管理服務(wù)具有最大的通用性,必須考慮多線程環(huán)境向楼,即并發(fā)問題查吊。
可以使用Synchronized關(guān)鍵字解決(更多解決方案參考多線程及并發(fā)的博客)。 - 多數(shù)據(jù)庫服務(wù)器:
對于大型的企業(yè)級應(yīng)用湖蜕,常常需要同時(shí)連接不同的數(shù)據(jù)庫(如連接oracle和sybase)逻卖。如何連接不同的數(shù)據(jù)庫呢?
解決方案:設(shè)計(jì)一個(gè)符合單例模式的連接池管理類昭抒,在連接池管理類的唯一實(shí)例被創(chuàng)建時(shí)讀取一個(gè)資源文件评也,其中資源文件中存放著多個(gè)數(shù)據(jù)庫的url地址等信息。根據(jù)資源文件提供的信息灭返,創(chuàng)建多個(gè)連接池類的實(shí)例盗迟,每一個(gè)實(shí)例都是一個(gè)特定數(shù)據(jù)庫的連接池。連接池管理類實(shí)例為每個(gè)連接池實(shí)例取一個(gè)名字熙含,通過不同的名字來管理不同的連接池罚缕。
- 多用戶環(huán)境:
對于同一個(gè)數(shù)據(jù)庫有多個(gè)用戶使用不同的名稱和密碼訪問的情況,也可以通過資源文件處理怎静。 - 事務(wù)處理:
在java語言中邮弹,connection類本身提供了對事務(wù)的支持黔衡,可以通過設(shè)置connection的autocommit屬性為false 然后顯式的調(diào)用commit或rollback方法來實(shí)現(xiàn)。但要高效的進(jìn)行connection復(fù)用腌乡,就必須提供相應(yīng)的事務(wù)支持機(jī)制盟劫。可采用每一個(gè)事務(wù)獨(dú)占一個(gè)連接來實(shí)現(xiàn)与纽,這種方法可以大大降低事務(wù)管理的復(fù)雜性侣签。 -
連接池的分配與釋放
連接池的分配與釋放,對系統(tǒng)的性能有很大的影響急迂。合理的分配與釋放硝岗,可以提高連接的復(fù)用度,從而降低建立新連接的開銷袋毙,同時(shí)還可以加快用戶的訪問速度型檀。
解決方案示意圖:
解決方案描述:
對于連接的管理可使用空閑池。即把已經(jīng)創(chuàng)建但尚未分配出去的連接按創(chuàng)建時(shí)間存放到一個(gè)空閑池中听盖。每當(dāng)用戶請求一個(gè)連接時(shí)胀溺,系統(tǒng)首先檢查空閑池內(nèi)有沒有空閑連接。如果有就把建立時(shí)間最長(通過容器的順序存放實(shí)現(xiàn))的那個(gè)連接分配給他(實(shí)際是先做連接是否有效的判斷皆看,如果可用就分配給用戶仓坞,如不可用就把這個(gè)連接從空閑池刪掉,重新檢測空閑池是否還有連接)腰吟;如果沒有則檢查當(dāng)前所開連接池是否達(dá)到連接池所允許的最大連接數(shù)(maxconn)如果沒有達(dá)到无埃,就新建一個(gè)連接,如果已經(jīng)達(dá)到毛雇,就等待一定的時(shí)間(timeout)嫉称。如果在等待的時(shí)間內(nèi)有連接被釋放出來就可以把這個(gè)連接分配給等待的用戶,如果等待時(shí)間超過預(yù)定時(shí)間timeout 則返回空值(null)灵疮。系統(tǒng)對已經(jīng)分配出去正在使用的連接只做計(jì)數(shù)织阅,當(dāng)使用完后再返還給空閑池。對于空閑連接的狀態(tài)震捣,可開辟專門的線程定時(shí)檢測荔棉,這樣會花費(fèi)一定的系統(tǒng)開銷,但可以保證較快的響應(yīng)速度蒿赢。也可采取不開辟專門線程润樱,只是在分配前檢測的方法。
- 連接池的配置與維護(hù):
最大連接數(shù)是連接池中允許連接的最大數(shù)目羡棵,具體設(shè)置多少壹若,要看系統(tǒng)的訪問量,可通過反復(fù)測試,找到最佳點(diǎn)舌稀。
如何確保連接池中的最小連接數(shù)呢啊犬?有動態(tài)和靜態(tài)兩種策略灼擂。動態(tài)即每隔一定時(shí)間就對連接池進(jìn)行檢測壁查,如果發(fā)現(xiàn)連接數(shù)量小于最小連接數(shù),則補(bǔ)充相應(yīng)數(shù)量的新連接以保證連接池的正常運(yùn)轉(zhuǎn)剔应。靜態(tài)是發(fā)現(xiàn)空閑連接不夠時(shí)再去檢查睡腿。
3)開發(fā)中最好使用成熟的連接池:
- 已經(jīng)存在很多流行的性能優(yōu)良的第三方數(shù)據(jù)庫連接池jar包供我們使用
- DBCP:
http://commons.apache.org/proper/commons-dbcp/ - C3P0:
http://mvnrepository.com/artifact/com.mchange/c3p0
3.DBCP的簡介及環(huán)境搭建
1)DBCP簡介:
- DBCP(DataBase connection pool)數(shù)據(jù)庫連接池是 apache 上的一個(gè)Java連接池項(xiàng)目。DBCP通過連接池預(yù)先同數(shù)據(jù)庫建立一些連接放在內(nèi)存中(即連接池中)峻贮,應(yīng)用程序需要建立數(shù)據(jù)庫連接時(shí)直接到從接池中申請一個(gè)連接使用席怪,用完后由連接池回收該連接,從而達(dá)到連接復(fù)用纤控,減少資源消耗的目的挂捻。
- 目前的DBCP已經(jīng)出到1.4版本。
而dbcp2與dbcp并不是完全相同的船万,用法也不同刻撒。
dbcp2已經(jīng)出到2.2.0版本。
dbcp2需要jdk1.7版本的支持耿导,否則會報(bào)錯(cuò)声怔。
2)測試環(huán)境搭建(dbcp2):
非Maven環(huán)境下只需要?jiǎng)?chuàng)建項(xiàng)目并且導(dǎo)入jar包就可以了。
要導(dǎo)入的jar包(dbcp2):
commons-dbcp2.jar
commons-pool2.jar
dbcp則導(dǎo)入:
commons-dbcp.jar
commons-pool.jar
還看見有博客說要導(dǎo)入:
commons-logging.jar
commons-collections.jar
那就都加上唄舱呻,反正多了無害,等以后有空了再來細(xì)細(xì)研究這些jar包的作用醋火。-
Maven環(huán)境下,我就只創(chuàng)建簡單的java工程來測試了箱吕,pom.xml依賴如下:
... <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> <version>2.2.0</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency> ...
這樣就搭建完畢了芥驳。
4.簡單使用dbcp2
在src/main/resources下創(chuàng)建dbcp.properties文件
-
dbcp.properties文件配置如下:
######## DBCP配置文件 ########## # 驅(qū)動名 driverClassName=com.mysql.jdbc.Driver # url url=jdbc:mysql://127.0.0.1:3306/springtest01?useUnicode=true&characterEncoding=utf-8 # 用戶名 username=root # 密碼 password=root # 初始連接數(shù) initialSize=30 # 最大活躍數(shù) maxTotal=30 # 最大空閑數(shù) maxIdle=10 # 最小空閑數(shù) minIdle=5 # 最長等待時(shí)間(毫秒) maxWaitMillis=1000 # 程序中的連接不使用后是否被連接池回收(該版本要使用removeAbandonedOnMaintenance和removeAbandonedOnBorrow) # removeAbandoned=true removeAbandonedOnMaintenance=true removeAbandonedOnBorrow=true # 連接在所指定的秒數(shù)內(nèi)未使用才會被刪除(秒) removeAbandonedTimeout=100
-
創(chuàng)建工具類DBCPUtil:
package DBCP2; import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp2.BasicDataSourceFactory; public class DBCPUtil { private static Properties properties = new Properties(); private static DataSource dataSource; //加載DBCP配置文件 static{ try{ //注意這里需要使用絕對路徑 FileInputStream is = new FileInputStream("src/main/resources/dbcp.properties"); properties.load(is); }catch(IOException e){ e.printStackTrace(); } //獲取數(shù)據(jù)源對象 try{ dataSource = BasicDataSourceFactory.createDataSource(properties); }catch(Exception e){ e.printStackTrace(); } } //從連接池中獲取一個(gè)連接 public static Connection getConnection(){ Connection connection = null; try{ connection = dataSource.getConnection(); }catch(SQLException e){ e.printStackTrace(); } try { connection.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
-
測試類:
package DBCP2; import java.sql.Connection; import org.junit.Assert; import org.junit.Test; import DBCP2.DBCPUtil; public class DBCPTest { @Test public void testConn(){ Connection conn=DBCPUtil.getConnection(); Assert.assertNotNull(conn); } }
測試結(jié)果:
5.dbcp2的另一種使用方式
其實(shí)還有很多種方式,只要能夠加載配置就可以了茬高。
-
創(chuàng)建DBCPUtil2如下:
package DBCP2; import java.sql.Connection; import java.sql.SQLException; import org.apache.commons.dbcp2.BasicDataSource; public class DBCPUtil2{ private static BasicDataSource dataSource=new BasicDataSource(); //加載DBCP配置文件 static{ //獲取數(shù)據(jù)源對象 try{ dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/springtest?useUnicode=true&characterEncoding=utf-8"); dataSource.setUsername("root"); dataSource.setPassword("root"); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); //初始連接數(shù) dataSource.setMaxTotal(30); //最大空閑數(shù) dataSource.setMaxIdle(10); //最小空閑數(shù) dataSource.setMinIdle(5); //最長等待時(shí)間(ms) dataSource.setMaxWaitMillis(10000); //指定時(shí)間內(nèi)未使用連接則關(guān)閉(s) dataSource.setRemoveAbandonedTimeout(100); //程序中的連接不使用后是否被連接池回收 dataSource.setRemoveAbandonedOnBorrow(true); dataSource.setRemoveAbandonedOnMaintenance(true); }catch(Exception e){ e.printStackTrace(); } } //從連接池中獲取一個(gè)連接 public static Connection getConnection(){ Connection connection = null; try{ connection = dataSource.getConnection(); }catch(SQLException e){ e.printStackTrace(); } try { connection.setAutoCommit(false); } catch (SQLException e) { e.printStackTrace(); } return connection; } }
-
測試代碼:
package DBCP2; import java.sql.Connection; import org.junit.Assert; import org.junit.Test; public class DBCPTest2 { @Test public void testConn(){ Connection conn=DBCPUtil2.getConnection(); System.out.println(conn); Assert.assertNotNull(conn); } }
-
測試結(jié)果:
其他使用方式自行探索
其實(shí)JNDI也算一種晚树,因?yàn)樵赥omcat中內(nèi)置了DBCP2連接池。獲取連接之后的事情雅采,就是和JDBC的實(shí)現(xiàn)方式相同了爵憎。
-
總結(jié)這兩種種方式的思維圖:
6.DBCP連接池在Spring中的基本配置
參考博客:
http://happyqing.iteye.com/blog/2304131
Spring使用dbcp
-
首先需要導(dǎo)包:
<dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.5.0</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.38</version> </dependency>
-
xml文件:
<!-- 屬性文件配置 --> <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:jdbc.properties</value> </list> </property> </bean> <!-- dbcp --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driverClassName}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> <!-- 連接初始值,連接池啟動時(shí)創(chuàng)建的連接數(shù)量的初始值 默認(rèn)值是0 --> <property name="initialSize" value="3" /> <!-- 最小空閑值.當(dāng)空閑的連接數(shù)少于閥值時(shí)婚瓜,連接池就會預(yù)申請去一些連接宝鼓,以免洪峰來時(shí)來不及申請 默認(rèn)值是0 --> <property name="minIdle" value="3" /> <!-- 最大空閑值.當(dāng)經(jīng)過一個(gè)高峰時(shí)間后,連接池可以慢慢將已經(jīng)用不到的連接慢慢釋放一部分巴刻,一直減少到maxIdle為止 愚铡,0時(shí)無限制 默認(rèn)值是8 --> <property name="maxIdle" value="5" /> <!-- 連接池的最大值,同一時(shí)間可以從池分配的最多連接數(shù)量,0時(shí)無限制 默認(rèn)值是8 --> <property name="maxActive" value="15" /> </bean>
-
jdbc.properties:
# dbcp driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/springtest01?useUnicode=true&characterEncoding=utf-8 username=root password=password
Spring使用dbcp2
1)xml配置方式:
導(dǎo)包
-
xml配置:
<!-- dbcp2 --> <bean id="dataSource2" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${driverClassName}" /> <property name="url" value="${url}" /> <property name="username" value="${username}" /> <property name="password" value="${password}" /> <!-- 連接初始值沥寥,連接池啟動時(shí)創(chuàng)建的連接數(shù)量的初始值 默認(rèn)值是0 --> <property name="initialSize" value="3" /> <!-- 最小空閑值.當(dāng)空閑的連接數(shù)少于閥值時(shí)碍舍,連接池就會預(yù)申請去一些連接,以免洪峰來時(shí)來不及申請 默認(rèn)值是0 --> <property name="minIdle" value="3" /> <!-- 最大空閑值.當(dāng)經(jīng)過一個(gè)高峰時(shí)間后邑雅,連接池可以慢慢將已經(jīng)用不到的連接慢慢釋放一部分片橡,一直減少到maxIdle為止 ,0時(shí)無限制 默認(rèn)值是8 --> <property name="maxIdle" value="5" /> <!-- 連接池的最大值淮野,同一時(shí)間可以從池分配的最多連接數(shù)量捧书,0時(shí)無限制 默認(rèn)值是8 --> <property name="maxTotal" value="15" /> </bean>
注意和dbcp的區(qū)別:maxActive改為了maxTotal
-
jdbc.properties代碼:
# dbcp driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/springtest01?useUnicode=true&characterEncoding=utf-8 username=root password=password
2)JavaConfig配置方式:
javaConfig的兩種寫法,其實(shí)和上面的DBCPUtil的那兩種寫法差不多骤星。
-
簡單的寫法:
@Configuration public class SqlConfig { @Bean private DataSource dataSource(){ BasicDataSource dataSource=new BasicDataSource(); dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/springtest?useUnicode=true&characterEncoding=utf-8"); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUsername("root"); dataSource.setPassword("root"); dataSource.setInitialSize(20); return dataSource; } }
得到了DataSource對象就可以通過該對象Bean得到連接经瓷。