1. 程式人生 > >JDBC(三)資料庫連線池(DBCP、C3P0)

JDBC(三)資料庫連線池(DBCP、C3P0)

前言

  這段時間狀態有一點浮躁,希望自己靜下心來。還有特別多的東西還沒有學懂。需要學習的東西非常的多,加油!

一、JDBC複習

  Java Data Base Connectivity,java資料庫連線,在需要儲存一些資料,或者拿到一些資料的時候,就需要往資料庫裡存取資料。那麼java如何連線資料庫呢?需要哪些步驟?

1.1、註冊驅動

  1)什麼是驅動

     驅動就是JDBC實現類,通俗點講,就是能夠連線到資料庫功能的東西就是驅動,由於市面上有很多資料庫,Oracle、MySql等等,所以java就有一個連線資料庫的實現規

    範介面,定義一系列的連線資料庫介面(java.sql.Driver介面),但是不提供實現,而每個資料庫廠家來提供這些介面的具體實現

,這樣一來,不管使用的是什麼資料庫,我

    們開發者寫的程式碼都是相同的,就不必因為資料庫的不同,而寫法不同,唯一的不同就是資料庫驅動不一樣,使用mysql,那麼就必須使用mysql的驅動,使用Oracle就必

    須使用oracle的驅動實現類。

    看下面mysql連線資料的原理圖,看看驅動是在哪裡,起什麼作用?

    

  2)DriverManager,一個工具類,是用於操作管理JDBC實現類的

 原始寫法:DriverManager.register(new Driver());  //因為使用的是MySql,所以在導包時就需要匯入com.mysql.jdbc.Driver
 現在寫法:Class.forName("com.mysql.jdbc.Driver");  //不用導包,會執行com.mysql.jdbc.Driver類中的靜態程式碼塊,其靜態程式碼塊的內容為    static {       try {                  java.sql.DriverManager.registerDriver(new Driver());                  } catch (SQLException E) {                     
throw new RuntimeException("Can't register driver!");          }          }  

  分析: 

   會發現第二種載入驅動的方法的底層其實就是第一種載入驅動。為什麼要這樣呢?原因很簡單, 第一種是硬程式設計,直接將資料庫驅動給寫死了,無法擴充套件,如果使用第一

   種,那麼連線的資料庫只能是mysql,因為導包導的是mysql的驅動包,如果換成Oracle,就會報錯,需要在程式碼中將Oracle的驅動包匯入,這樣很麻煩,而第二種寫法就不

   一樣了,第二種是使用的字串方法註冊驅動的,我們只需要將該字串提取到一個配置檔案中,以後想換成oracle資料庫,只需要將該字串換成oracle驅動的類全名即可

   ,而不需要到程式碼中去修改什麼東西。

1.2、獲取連線

  使用DriverManage來獲得連線,因為DriverManager是驅動實現類的管理者

  Connection conn = DriverManager.getConnection(url,user,password);

    url:確定資料庫伺服器的位置,埠號,資料庫名

      jdbc:mysql://localhost:3306/db 

    user:登入名稱,預設root

    password:密碼,預設root   

  這裡只是說mysql,別的資料庫,url格式就不同了。

    MySQL    jdbc:mysql://localhost:3306/db    預設埠是3306,粗體為連線時使用的資料庫名

    Oracle     jdbc:oracle:thin:@localhost:1521:db  預設埠號1521

    DB2      jdbc:db2://localhost:6789/db      預設埠號6789

    SQLServer  jdbc:microsoft:sqlserver://localhost:1433;databaseName=db  預設埠號1433

    SQLServer 2005  jdbc:sqlserver://localhost:1433;databaseName=db  預設埠號1433

1.3、獲取執行sql物件,PreparedStatement物件 

  通過Connection物件獲取Statement或者PraparedStament物件(使用它)處理sql

  1)Statement

    Statement st = conn.createStatement();  //獲取sql語句執行物件

    st.excuteUpdate(sql);  //執行增刪改語句

    st.excuteQuery(sql);  //執行查詢語句      

    sql語句必須是完整的。

  2)PraparedStatment

    sql語句可以不是完整的,可以將引數用?替代,然後在預編譯後加入未知引數

    PraparedStatment ps = conn.prapareStatement(sql);  //獲取sql語句執行物件praparedStatment

    賦值

      ps.setInt(Index,value);  ps.setString(index,value);  //可以設定很多中型別,index從1開始,代表sql語句中的第幾個未知引數,

      ps.excuteUpdate();  //執行增刪改語句

      ps.excuteQuery(sql);  //執行查詢語句

  這兩個的區別,常使用的是PraparedStatment物件,因為它可以預編譯,效率高,可以設定引數等等優點。

1.4、獲取結果集物件

  int count = ps.excuteUpdate();   //執行增刪改的sql語句時,返回一個int型別的整數,代表資料庫表影響的行數,

  Result result = ps.excuteQuery();  //執行查詢sql語句時,返回一個結果集物件,該物件裝著所有查詢到的資料資訊,一行一行的儲存資料庫表資訊。

1.5、處理結果集

  對查詢到的Result結果進行處理,拿到所有資料,並封裝成物件。

        while(rs.next()){

          獲取行資料的第一種方式
          rs.getString(index);//index代表第幾列,從1開始

          獲取行資料的第二中方式
          rs.getString(string);  //string:代表欄位名稱。

        }

  

  總結:java的JDBC就分為5步,4個屬性

    屬性:driver、url、user、password

    五步:

      註冊驅動、獲取連線、獲取執行sql語句物件、獲取結果集物件、處理結果。

二、JDBC的CURD操作

  建立(Create)、更新(Update)、讀取(Retrieve)和刪除(Delete)操作

  查詢所有(讀取Retrieve)

2.1、查詢所有記錄讀取(Retrieve)

@Test
    public void findAll() throws Exception{
        //1 註冊驅動
        Class.forName("com.mysql.jdbc.Driver");
        //2 獲得連線
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
        //3語句執行者,sql語句
        Statement st = conn.praparedStatement("select * from t_user");
        //4 執行查詢語句
        ResultSet rs = st.executeQuery();
        //5處理資料
        // * 如果查詢多個使用,使用while迴圈進行所有資料獲取
        // * 技巧:如果查詢結果最多1條,使用  if(rs.next()) {  查詢到了 } else {  沒有資料 }
        while(rs.next()){
            int id = rs.getInt(1);
            String username = rs.getString(2);
            String password =rs.getString(3);
            System.out.print(id + ", ");
            System.out.print(username + ", ");
            System.out.println(password);
        }
        //6釋放資源
        rs.close();
        st.close();
        conn.close();
        
    }
findAll()

2.2、增加操作(建立Create)  

@Test
    public void save() throws Exception{
        //1 註冊驅動
        Class.forName("com.mysql.jdbc.Driver");
        //2 獲得連線
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
        //3語句執行者
        Statement st = conn.praparedStatement("insert into t_user(username,password) values(?,?)");
                //3.1賦值
                st.setString(1,"xiaoming");
                st.setString(2,"123");
        //4 執行DML語句
        int r = st.executeUpdate();
        
        //5處理資料
        System.out.println(r);
        
        //6釋放資源
        //rs.close();
        st.close();
        conn.close();
    }
save()

2.3、更新操作 (Update)

@Test
    public void update() throws Exception{
        //1 註冊驅動
        Class.forName("com.mysql.jdbc.Driver");
        //2 獲得連線
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
        //3語句執行者
        Statement st = conn.praparedStatement("update t_user set username = ? where id = ? ");
                //3.1賦值引數
                st.setString(1,"xiaoye");
                st.setInt(2,2);
        //4 執行DML語句
        int r = st.executeUpdate();
        
        //5處理資料
        System.out.println(r);
        
        //6釋放資源
        //rs.close();
        st.close();
        conn.close();
}
update()

2.4、刪除操作(delete)

@Test
    public void delete() throws Exception{
        //1 註冊驅動
        Class.forName("com.mysql.jdbc.Driver");
        //2 獲得連線
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "root");
        //3語句執行者
        Statement st = conn.praparedStatement("delete from t_user where id = ?");
                //3.1賦值引數
                st.setInt(1,2);
        //4 執行DML語句
        int r = st.executeUpdate();
        
        //5處理資料
        System.out.println(r);
        
        //6釋放資源
        //rs.close();
        st.close();
        conn.close();
}
delete()

  也可以用我前面寫的JDBC連線的工具類去連線!

三、資料庫連線池

  在上面,我們在進行CRUD時,一直重複性的寫一些程式碼,比如最開始的註冊驅動,獲取連線程式碼,一直重複寫,通過編寫一個獲取連線的工具類後,解決了這個問題,但是又

  會出現新的問題,每進行一次操作,就會獲取一個連線,用完之後,就銷燬,就這樣一直新建連線,銷燬連線,新建,銷燬,連線Connection 建立與銷燬 比較耗時的。所以應

  該要想辦法解決這個問題?

  解決方法:

    連線池就是為了解決這個問題而出現的一個方法,為了提高效能,開發連線池,連線池中一直保持有n個連線,供呼叫者使用,呼叫者用完返還給連線池,繼續給別的呼叫

    者使,比如連線池中一開始就有10個連線,當有5個使用者拿走了5個連線後,池中還剩5個,當第6個使用者在去池中拿連線而前面5個連線還沒歸還時,連線池就會新建一個

    連線給第六個使用者,讓池中一直能夠儲存最少5個連線,而當這樣新建了很多連線後,使用者歸還連接回來時,會比原先連線池中的10個連線更多,連線池就會設定一個池中

    最大空閒的連接數,如果超過了這個數,就會將超過的連線給釋放掉,連線池就是這樣工作的。

3.1、連線池概述

  資料庫連線池負責分配、管理和釋放資料庫連線,它允許應用程式重複使用一個現有的資料庫連線,而不是再重新建立一個釋放空閒時間超過最大空閒時間的資料庫連線來避

  免因為沒有釋放資料庫連線而引起的資料庫連線遺漏。這項技術能明顯提高對資料庫操作的效能。

3.2、比較應用程式直接獲取連線和使用連線池

  1)應用程式直接獲取連線

    

    缺點:使用者每次請求都需要向資料庫獲得連結,而資料庫建立連線通常需要消耗相對較大的資源,建立時間也較長。

          假設網站一天10萬訪問量,資料庫伺服器就需要建立10萬次連線,極大的浪費資料庫的資源,並且極易造成資料庫伺服器記憶體溢位、宕機。

  2)使用連線池連線

     

    目的:解決建立資料庫連線耗費資源和時間很多的問題,提高效能。

四、常用的資料庫連線池  

  現在很多WEB伺服器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現,即連線池的實現。通常我們把DataSource的實現,按其英文含義稱之為資料來源,

  資料來源中都包含了資料庫連線池的實現。
  也有一些開源組織提供了資料來源的獨立實現:
    DBCP 資料庫連線池
    C3P0 資料庫連線池
  實際應用時不需要編寫連線資料庫程式碼,直接從資料來源獲得資料庫的連線。程式設計師程式設計時也應儘量使用這些資料來源的實現,以提升程式的資料庫訪問效能。

  DBCP、C3P0、tomcat內建連線池(JNDI)是我們開發中會用到的。

4.1、DBCP連線池

  1)概述

    DBCP 是 Apache 軟體基金組織下的開源連線池實現,使用DBCP資料來源,應用程式應在系統中增加如下兩個 jar 檔案:
      Commons-dbcp.jar:連線池的實現
      Commons-pool.jar:連線池實現的依賴庫
    Tomcat 的連線池正是採用該連線池來實現的。該資料庫連線池既可以與應用伺服器整合使用,也可由應用程式獨立使用。

  1)獲取連線的兩種方式

    兩種方式獲得連線,使用配置檔案,不使用配置檔案

    1.1)不使用配置檔案,自己手動設定引數

       第一:導包

      

      第二:測試例子

package com.zyh.util;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.commons.dbcp.BasicDataSource;


public class TestDBCP {

    public static void main(String[] args) {
        Connection conn = TestDBCP.getConnection();
        try {
            String sql = "select id,name,price from book";
            PreparedStatement ps = conn.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();
            while(rs.next()){
                String id = rs.getString(1);
                String name = rs.getString(2);
                double price = rs.getDouble(3);
                System.out.println("id="+id+",name="+name+",price="+price);
                System.out.println("-------------------------");
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
        
    }
    
    public static Connection getConnection(){
        //建立核心類
                BasicDataSource bds = new BasicDataSource();
                //配置4個基本引數
                bds.setDriverClassName("com.mysql.jdbc.Driver");
                bds.setUrl("jdbc:mysql:///book");
                bds.setUsername("root");
                bds.setPassword("654321");
                
                //管理連線配置
                bds.setMaxActive(50); //最大活動數
                bds.setMaxIdle(20);  //最大空閒數
                bds.setMinIdle(5);  //最小空閒數
                bds.setInitialSize(10);  //初始化個數
                
                //獲取連線
                try {
                     Connection conn = bds.getConnection();
                     return conn;
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                    }
    }

}
TestDBCP

    1.2)使用配置檔案,引數寫入配置檔案中即可,也就是通過配置檔案來配置驅動、使用者名稱、密碼、等資訊  

      第一:導包

      和上面的一樣的

      第二:匯入配置檔案dbcpconfig.properties

#連線設定
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/book
username=root
password=654321

#<!-- 初始化連線 -->
initialSize=10

#最大連線數量
maxActive=50

#<!-- 最大空閒連線 -->
maxIdle=20

#<!-- 最小空閒連線 -->
minIdle=5

#<!-- 超時等待時間以毫秒為單位 6000毫秒/1000等於60秒 -->
maxWait=60000


#JDBC驅動建立連線時附帶的連線屬性屬性的格式必須為這樣:[屬性名=property;] 
#注意:"user""password" 兩個屬性會被明確地傳遞,因此這裡不需要包含他們。
connectionProperties=useUnicode=true;characterEncoding=gbk

#指定由連線池所建立的連線的自動提交(auto-commit)狀態。
defaultAutoCommit=true

#driver default 指定由連線池所建立的連線的只讀(read-only)狀態。
#如果沒有設定該值,則“setReadOnly”方法將不被呼叫。(某些驅動並不支援只讀模式,如:Informix)
defaultReadOnly=

#driver default 指定由連線池所建立的連線的事務級別(TransactionIsolation)。
#可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
dbcpconfig.properties

      第三:獲取連線,測試例子

package com.zyh.util;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

public class TestDBCPPro {

    public static void main(String[] args) {
        Connection conn = TestDBCPPro.getConnection();
        try {
            String sql = "select id,name,price from book";
            PreparedStatement ps = conn.prepareStatement(sql);
            ResultSet rs = ps.executeQuery();
            while(rs.next()){
                String id = rs.getString(1);
                String name = rs.getString(2);
                double price = rs.getDouble(3);
                System.out.println("id="+id+",name="+name+",price="+price);
                System.out.println("-------------------------");
            }
            
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    public static Connection getConnection(){
         //通過類載入器獲取指定配置檔案的輸入流,TestDBCPPro是一個類名
        InputStream in = TestDBCPPro.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
        Properties properties = new Properties();
        try {
                properties.load(in);
                //載入配置檔案,獲得配置資訊
                DataSource ds = BasicDataSourceFactory.createDataSource(properties);
                Connection conn = ds.getConnection();
                return conn;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    

}
TestDBCPPro

    1.3)編寫一個數據源工具類

package com.zyh.util;


import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

import javax.imageio.stream.FileImageInputStream;
import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

public class DBCPUtils {
    private static DataSource ds = null;
    static{
        Properties prop = new Properties();
        try {
            prop.load(DBCPUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"));//根據DBCPUtil的classes的路徑,載入配置檔案
            ds = BasicDataSourceFactory.createDataSource(prop);//得到一個數據源 
        } catch (Exception e) {
            throw new ExceptionInInitializerError("初始化錯誤,請檢查配置檔案");
        }
    }
    
    public static Connection getConnection(){
        try {
            return ds.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("伺服器忙。。。");
        }
    }
    
    public static void release(Connection conn,Statement stmt,ResultSet rs){
        //關閉資源
                if(rs!=null){
                    try {
                        rs.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    rs = null;
                }
                if(stmt!=null){
                    try {
                        stmt.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    stmt = null;
                }
                if(conn!=null){
                    try {
                        conn.close();//關閉
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    conn = null;
                }
    }
    
}
DBCPUtils

4.2、C3P0

  1)導包

  

  2)從配置資訊中獲取  配置檔案必須為xml(c3p0-config.xml)

<c3p0-config>
    <!-- 預設配置,如果沒有指定則使用這個配置 -->
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/book</property>
        <property name="user">root</property>
        <property name="password">654321</property>
    
        <property name="checkoutTimeout">30000</property>
        <property name="idleConnectionTestPeriod">30</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
        <property name="maxStatements">200</property>
        <user-overrides user="test-user">
            <property name="maxPoolSize">10</property>
            <property name="minPoolSize">1</property>
            <property name="maxStatements">0</property>
        </user-overrides>
    </default-config> 
    <!-- 命名的配置 -->
    <named-config name="jxpx">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql://localhost:3306/myums</property>
        <property name="user">root</property>
        <property name="password">root</property>
    <!-- 如果池中資料連線不夠時一次增長多少個 -->
        <property name="acquireIncrement">5</property>
        <property name="initialPoolSize">20</property>
        <property name="minPoolSize">10</property>
        <property name="maxPoolSize">40</property>
        <property name="maxStatements">0</property>
        <property name="maxStatementsPerConnection">5</property>
    </named-config>
</c3p0-config>
c3p0-config.xml  

    從配置檔案中看,需要注意一個地方,一個是default-config,一個是name-config,兩者都區別在於建立核心類物件時,如果將name-config作為引數傳進去,

    那麼將會呼叫name-config下的配置資訊,否則將呼叫default-config下的配置資訊,

    兩種方式使用c3p0,加引數,使用named-config 的配置資訊不加引數,自動載入配置資訊,載入的是default-config中的資訊。

        //1 c3p0...jar 將自動載入配置檔案。規定:WEB-INF/classes (src)  c3p0-config.xml,也就是將配置檔案放在src下就會自動載入。
         //ComboPooledDataSource dataSource = new ComboPooledDataSource(); //自動從配置檔案 <default-config>
         ComboPooledDataSource dataSource = new ComboPooledDataSource(); //手動指定配置檔案 <named-config name="jxpx">
         Connection conn = dataSource.getConnection();
         System.out.println(conn);

  3)編寫一個數據源工具類

  這個工具類的xml是:

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
  <default-config>
    <property name="driverClass">com.mysql.jdbc.Driver</property>
    <property name="jdbcUrl">jdbc:mysql://localhost:3306/book/property>
    <property name="user">root</property>
    <property name="password">654321</property>
    <property name="initialPoolSize">10</property>
    <property name="maxIdleTime">30</property>
    <property name="maxPoolSize">100</property>
    <property name="minPoolSize">10</property>

  </default-config>

</c3p0-config>
c3p0-config.xml

  工具類

package com.zyh.util;

import java.beans.PropertyVetoException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import javax.sql.DataSource;

import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0Util {
    //得到一個數據源
    private static DataSource dataSource = new ComboPooledDataSource();
    
    //從資料來源中得到一個連線物件
    public static Connection getConnection(){
        try {
            return dataSource.getConnection();
        } catch (SQLException e) {
            throw new RuntimeException("伺服器錯誤");
        }
    }
    
    public static void release(Connection conn,Statement stmt,ResultSet rs){
        //關閉資源
                if(rs!=null){
                    try {
                        rs.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    rs = null;
                }
                if(stmt!=null){
                    try {
                        stmt.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    stmt = null;
                }
                if(conn!=null){