若是用戶每次請求都向數據庫得到鏈接,而數據庫建立鏈接一般須要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,數據庫服務器就須要建立10萬次鏈接,極大的浪費數據庫的資源,而且極易形成數據庫服務器內存溢出、拓機。以下圖所示:html
數據庫鏈接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現的尤其突出.對數據庫鏈接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標。數據庫鏈接池正式針對這個問題提出來的。java
數據庫鏈接池負責分配,管理和釋放數據庫鏈接,它容許應用程序重複使用一個現有的數據庫鏈接,而不是從新創建一個。以下圖所示:mysql
有了池,咱們就不用本身來建立Connection,而是經過池來獲取Connection對象。當使用完Connection後,調用Connection的close()方法也不會真的關閉Connection,而是把Connection「歸還」給池。池就能夠再利用這個Connection對象了。sql
數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中, 這些數據庫鏈接的數量是由最小數據庫鏈接數來設定的.不管這些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量.鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中.數據庫
數據庫鏈接池的最小鏈接數和最大鏈接數的設置要考慮到如下幾個因素:apache
【實現思路】 設計模式
【代碼編寫】緩存
public class MyDataSource implements DataSource { //1.建立1個容器用於存儲Connection對象 private static LinkedList<Connection> pool = new LinkedList<Connection>(); //2.建立5個鏈接放到容器中去 static{ for (int i = 0; i < 5; i++) { Connection connection = JdbcUtils.getConnection(); pool.add(connection); } } /** * 重寫獲取鏈接的方法 */ @Override public Connection getConnection() throws SQLException { Connection connection = null; //3.使用前先判斷 if (pool.size() == 0) { //4.池子裏面沒有,咱們再建立一些 for (int i = 0; i < 5; i++) { connection = JdbcUtils.getConnection(); pool.add(connection); } } //5.從池子裏面獲取一個鏈接對象Connection connection = pool.remove(0); return connection; } /** * 歸還鏈接對象到鏈接池中去 */ public void backConnection(Connection connection) { pool.add(connection); } ... }
【測試】tomcat
@Test public void testAddUser() { Connection conn = null; PreparedStatement pstmt = null; // 1.建立自定義鏈接池對象 MyDataSource dataSource = new MyDataSource(); try { // 2.從池子中獲取鏈接 conn = dataSource.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 3); pstmt.setString(2, "張三"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } else { System.out.println("添加失敗"); } } catch (Exception e) { e.printStackTrace(); }finally { dataSource.backConnection(conn); } }
【需求】服務器
上述自定義鏈接池中存在嚴重問題,用戶調用getConnection()得到鏈接後,必須使用backConnection()方法進行鏈接的歸還,若是用戶調用conn.close()將鏈接真正的釋放,鏈接池中將出現無鏈接可用。
此時咱們但願,即便用戶調用了close()方法,鏈接仍歸還給鏈接池。close()方法原有功能是釋放資源,咱們指望的功能是將鏈接歸還給鏈接池。說明close()方法沒有咱們但願的功能,咱們將對close()方法進行加強,從而實現將鏈接歸還給鏈接池的功能。
【方法加強總結】
1.繼承:
2.裝飾者模式:
3.動態代理:
【裝飾者設計模式】
設計模式:專門爲解決某一類問題,而編寫的固定格式的代碼。
固定結構:接口A,已知實現類C,須要裝飾者建立代理類B
實現步驟:
A a = ...C; B b = new B(a); class B implements A{ private A a; public B(A,a){ this.a = a; } // 加強的方法 public void close(){ } // 不須要加強的方法 public void commit(){ this.a.commit(); } }
【裝飾類】
//1.實現同一個接口Connection public class MyConnection implements Connection { //3.定義一個變量 private Connection conn; private LinkedList<Connection> pool; //2.編寫一個構造方法(參數使用了面向對象的多態特性) public MyConnection(Connection conn,LinkedList<Connection> pool) { this.conn = conn; this.pool = pool; } //4.書寫須要加強的方法 @Override public void close() throws SQLException { pool.add(conn); } /** * 5.實現不須要加強的方法 * 注意:此方法必須覆蓋!不然會出現空指針異常!!! */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); } @Override public void commit() throws SQLException { }
...
}
【使用裝飾類】
public class MyDataSource1 implements DataSource { //1.建立1個容器用於存儲Connection對象 private static LinkedList<Connection> pool = new LinkedList<Connection>(); //2.建立5個鏈接放到容器中去 static{ for (int i = 0; i < 5; i++) { Connection conn = JdbcUtils.getConnection(); //放入池子中connection對象已經通過改造了 MyConnection myconn = new MyConnection(conn, pool); pool.add(myconn); } } /** * 重寫獲取鏈接的方法 */ @Override public Connection getConnection() throws SQLException { Connection conn = null; //3.使用前先判斷 if (pool.size() == 0) { //4.池子裏面沒有,咱們再建立一些 for (int i = 0; i < 5; i++) { conn = JdbcUtils.getConnection(); //放入池子中connection對象已經通過改造了 MyConnection myconn = new MyConnection(conn, pool); pool.add(myconn); } } //5.從池子裏面獲取一個鏈接對象Connection conn = pool.remove(0); return conn; } /** * 歸還鏈接對象到鏈接池中去 */ public void backConnection(Connection connection) { pool.add(connection); } ... }
【測試】
/** * 使用改造過的connection */ @Test public void testAddUser1() { Connection conn = null; PreparedStatement pstmt = null; // 1.建立自定義鏈接池對象 DataSource dataSource = new MyDataSource1(); try { // 2.從池子中獲取鏈接 conn = dataSource.getConnection(); String sql = "insert into t_user values(?,?)"; //3.必須在自定義的connection類中重寫prepareStatement(sql)方法 pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 6); pstmt.setString(2, "王五"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } else { System.out.println("添加失敗"); } } catch (Exception e) { throw new RuntimeException(e); } finally { //這裏的connection是已經通過加強的,加強後的conn.close()就是將鏈接歸還給鏈接池 JdbcUtils.release(conn, pstmt, null); } }
DBCP 是 Apache 軟件基金組織下的開源鏈接池實現,在企業開發中也比較常見,它是tomcat內置的鏈接池
#基本配置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb1 username=root password=123 #初始化池大小,即一開始池中就會有10個鏈接對象 默認值爲0 initialSize=0 #最大鏈接數,若是設置maxActive=50時,池中最多能夠有50個鏈接,固然這50個鏈接中包含被使用的和沒被使用的(空閒) #你是一個包工頭,你一共有50個工人,但這50個工人有的當前正在工做,有的正在空閒 #默認值爲8,若是設置爲非正數,表示沒有限制!即無限大 maxActive=8 #最大空閒鏈接 #當設置maxIdle=30時,你是包工頭,你容許最多有20個工人空閒,若是如今有30個空閒工人,那麼要開除10個 #默認值爲8,若是設置爲負數,表示沒有限制!即無限大 maxIdle=8 #最小空閒鏈接 #若是設置minIdel=5時,若是你的工人只有3個空閒,那麼你須要再去招2個回來,保證有5個空閒工人 #默認值爲0 minIdle=0 #最大等待時間 #當設置maxWait=5000時,如今你的工做都出去工做了,又來了一個工做,須要一個工人。 #這時就要等待有工人回來,若是等待5000毫秒還沒回來,那就拋出異常 #沒有工人的緣由:最多工人數爲50,已經有50個工人了,不能再招了,但50人都出去工做了。 #默認值爲-1,表示無限期等待,不會拋出異常。 maxWait=-1 #鏈接屬性 #就是原來放在url後面的參數,可使用connectionProperties來指定 #若是已經在url後面指定了,那麼就不用在這裏指定了。 #useServerPrepStmts=true,MySQL開啓預編譯功能 #cachePrepStmts=true,MySQL開啓緩存PreparedStatement功能, #prepStmtCacheSize=50,緩存PreparedStatement的上限 #prepStmtCacheSqlLimit=300,當SQL模板長度大於300時,就再也不緩存它 connectionProperties=useUnicode=true;characterEncoding=UTF8;useServerPrepStmts=true;cachePrepStmts=true;prepStmtCacheSize=50;prepStmtCacheSqlLimit=300 #鏈接的默認提交方式 #默認值爲true defaultAutoCommit=true #鏈接是否爲只讀鏈接 #Connection有一對方法:setReadOnly(boolean)和isReadOnly() #若是是隻讀鏈接,那麼你只能用這個鏈接來作查詢 #指定鏈接爲只讀是爲了優化!這個優化與併發事務相關! #若是兩個併發事務,對同一行記錄作增、刪、改操做,是否是必定要隔離它們啊? #若是兩個併發事務,對同一行記錄只作查詢操做,那麼是否是就不用隔離它們了? #若是沒有指定這個屬性值,那麼是否爲只讀鏈接,這就由驅動本身來決定了。即Connection的實現類本身來決定! defaultReadOnly=false #指定事務的事務隔離級別 #可選值:NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE #若是沒有指定,那麼由驅動中的Connection實現類本身來決定 defaultTransactionIsolation=REPEATABLE_READ
public class DBCPUtils { /** * 在java中,編寫數據庫鏈接池需實現java.sql.DataSource接口,每一種數據庫鏈接池都是DataSource接口的實現 * DBCP鏈接池就是java.sql.DataSource接口的一個具體實現 */ private static DataSource dataSource = null; //在靜態代碼塊中建立數據庫鏈接池 static{ try { //加載dbcp.properties配置文件 InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("dbcp.properties"); Properties prop = new Properties(); prop.load(in); //建立數據源 dataSource = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { e.printStackTrace(); } } /** * 從數據源中獲取數據庫鏈接 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 釋放資源 * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 */ public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
public class DBCPTest { @Test public void addUser() { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // 獲取數據庫鏈接 conn = DBCPUtils.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 8); pstmt.setString(2, "劉備"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } } catch (Exception e) { e.printStackTrace(); } } }
C3P0是一個開源的JDBC鏈接池,它實現了數據源和JNDI綁定,支持JDBC3規範和JDBC2的標準擴展。目前使用它的開源項目有Hibernate,Spring等。C3P0數據源在項目開發中使用得比較多。
若是操做的是Oracle數據庫,那麼還須要導入c3p0-oracle-thin-extras-0.9.2-pre1.jar
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!--這是默認配置信息--> <default-config> <!--鏈接四大參數配置--> <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="user">root</property> <property name="password">root</property> <!--池參數配置--> <!--若是池中數據鏈接不夠時一次增加多少個--> <property name="acquireIncrement">3</property> <!--初始化鏈接數--> <property name="initialPoolSize">10</property> <!--最小鏈接數--> <property name="minPoolSize">2</property> <!--最大鏈接數--> <property name="maxPoolSize">10</property> </default-config> <!--專門爲Oracle提供的配置信息--> <named-config name="oracle-config"> <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="user">root</property> <property name="password">123</property> <property name="acquireIncrement">3</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">2</property> <property name="maxPoolSize">10</property> </named-config> </c3p0-config>
public class C3P0Utils { private static ComboPooledDataSource dataSource = null; //在靜態代碼塊中建立數據庫鏈接池 static{ try { // 經過代碼建立C3P0鏈接池 /*dataSource = new ComboPooledDataSource(); dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test"); dataSource.setUser("root"); dataSource.setPassword("root"); dataSource.setInitialPoolSize(10); dataSource.setMinPoolSize(5); dataSource.setMaxPoolSize(20);*/ //經過讀取C3P0的xml配置文件建立數據源,C3P0的xml配置文件c3p0-config.xml必須放在src目錄下 //使用C3P0的默認配置來建立數據源 dataSource = new ComboPooledDataSource(); //使用名爲oracle-config的配置來建立數據源 // dataSource = new ComboPooledDataSource("oracle-config"); } catch (Exception e) { e.printStackTrace(); } } /** * 從數據源中獲取數據庫鏈接 */ public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } /** * 釋放資源 * 釋放的資源包括Connection數據庫鏈接對象,負責執行SQL命令的Statement對象,存儲查詢結果的ResultSet對象 */ public static void release(Connection conn, Statement st, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (st != null) { try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
@Test public void addUser() { Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { // 獲取數據庫鏈接 conn = C3P0Utils.getConnection(); String sql = "insert into t_user values(?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setInt(1, 9); pstmt.setString(2, "關羽"); int num = pstmt.executeUpdate(); if (num > 0) { System.out.println("添加成功"); } } catch (Exception e) { e.printStackTrace(); } }
在實際開發中,咱們有時候還會使用服務器提供給咱們的數據庫鏈接池,好比咱們但願Tomcat服務器在啓動的時候能夠幫咱們建立一個數據庫鏈接池,那麼咱們在應用程序中就不須要手動去建立數據庫鏈接池,直接使用Tomcat服務器建立好的數據庫鏈接池便可。要想讓Tomcat服務器在啓動的時候幫咱們建立一個數據庫鏈接池,那麼須要簡單配置一下Tomcat服務器。
JNDI(Java Naming and Directory Interface),Java命名和目錄接口,它對應於J2SE中的javax.naming包,
這套API的主要做用在於:它能夠把Java對象放在一個容器中(JNDI容器),併爲容器中的java對象取一個名稱,之後程序想得到Java對象,只需 經過名稱檢索便可。其核心API爲Context,它表明JNDI容器,其lookup方法爲檢索容器中對應名稱的對象。
Tomcat服務器建立的數據源是以JNDI資源的形式發佈的,因此說在Tomat服務器中配置一個數據源實際上就是在配置一個JNDI資源,經過查看Tomcat文檔,咱們知道使用以下的方式配置tomcat服務器的數據源:
<Context> <Resource name="jdbc/datasource" auth="Container" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test" maxActive="8" maxIdle="4"/> </Context>
服務器建立好數據源以後,咱們的應用程序又該怎麼樣獲得這個數據源呢,Tomcat服務器建立好數據源以後是以JNDI的形式綁定到一個JNDI容器中的,咱們能夠把JNDI想象成一個大大的容器,咱們能夠往這個容器中存放一些對象,一些資源,JNDI容器中存放的對象和資源都會有一個獨一無二的名稱,應用程序想從JNDI容器中獲取資源時,只須要告訴JNDI容器要獲取的資源的名稱,JNDI根據名稱去找到對應的資源後返回給應用程序。咱們平時作javaEE開發時,服務器會爲咱們的應用程序建立不少資源,好比request對象,response對象,服務器建立的這些資源有兩種方式提供給咱們的應用程序使用:第一種是經過方法參數的形式傳遞進來,好比咱們在Servlet中寫的doPost和doGet方法中使用到的request對象和response對象就是服務器以參數的形式傳遞給咱們的。第二種就是JNDI的方式,服務器把建立好的資源綁定到JNDI容器中去,應用程序想要使用資源時,就直接從JNDI容器中獲取相應的資源便可。
對於上面的name="jdbc/datasource"數據源資源,在應用程序中能夠用以下的代碼去獲取
Context initCtx = new InitialContext(); Context envCtx = (Context) initCtx.lookup("java:comp/env"); dataSource = (DataSource)envCtx.lookup("jdbc/datasource");
此種配置下,數據庫的驅動jar文件需放置在tomcat的lib下
<Context> <!--name:指定資源的名稱 factory:用來建立資源的工廠,這個值基本上是固定的,不用修改 type:資源的類型 其餘的東西都是資源的參數 --> <Resource name="jdbc/datasource" factory="org.apache.naming.factory.BeanFactory" type="javax.sql.DataSource" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test" maxActive="8" maxIdle="4"/> </Context>
/** * 獲取JNDI的資源 * */ public class AServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { /* * 1. 建立JNDI的上下文對象 */ try { Context cxt = new InitialContext(); // 2查詢出入口 // Context envContext = (Context)cxt.lookup("java:comp/env"); // 3. 再進行二次查詢,找到咱們的資源 // 使用的是名稱與<Resource>元素的name對應 // DataSource dataSource = (DataSource)envContext.lookup("jdbc/dataSource"); DataSource dataSource = (DataSource)cxt.lookup("java:comp/env/jdbc/dataSource"); Connection con = dataSource.getConnection(); System.out.println(con); con.close(); } catch (Exception e) { throw new RuntimeException(e); } } }
參考:https://www.cnblogs.com/xdp-gacl/p/4002804.html