1. 程式人生 > >javaweb-事務和連線池

javaweb-事務和連線池

一、事務

什麼是事務?

事務,一般是指要做的或所做的事情。在計算機術語中是指訪問並可能更新資料庫中各種資料項的一個程式執行單元。這些單元要麼全都成功,要麼全都不成功。

做一件事情,這個一件事情中有多個組成單元,這個多個組成單元要不同時成功,要不同時失敗。A賬戶轉給B賬戶錢,將A賬戶轉出錢的操作與B賬戶轉入錢的操作繫結到一個事務中,要不這兩個動作同時成功,代表這次轉賬成功,要不就兩個動作同時失敗,代表這次轉賬失敗。

事務在開發中的作用

下面來舉例說明什麼是事務,如下所示:

現實生活中的銀行轉賬業務,張三要給李四轉賬1000元,而在程式設計師眼中兩條SQL語句就可以搞定,如下:

l 給張三的賬戶減去1000元;

l 給李四的賬戶加上1000元;

如果在轉賬的業務中,成功的將張三的賬戶減去1000元,而在給李四的賬戶加1000元的時候,程式出現了問題,李四的賬戶沒有加上1000元,而張三的賬戶卻減掉了1000元,在現實生活中,這種情況肯定是不允許存在的。當我們將這個轉賬業務放在一個事務中,就不會出現以上情況了。

事務中有多個操作,這些操作要麼全部成功,要麼全部失敗,也就是說給張三的賬戶減去1000元如果成功了,那麼給李四的賬戶加上1000元的操作也必須是成功的,否則給張三減去1000元,以及給李四加上1000元都必須是失敗的

mysql的事務控制

MySQL下如何開啟事務 方式一:       start  transaction   開啟事務
      rollback    事務回滾(將資料恢復到事務開始時的狀態)       commit    事務提交(對事務中進行操作,進行確認操作,事務在提交後,資料就不可再進行恢復) 方式二:      show variables like '%commit%';   可以檢視當前autocommit 值      在MySQL資料庫中它的預設值是 "on" 代表自動事務。      自動事務的意義就是:執行任意一條SQL語句都會自動提交事務。      測試:將autocommit的值設定成off            1. set autocommit=off              2. 必須手動commit才可以將事務提交
           注意:MySQL 預設autocommit=on   oracle預設的autocommit=off                      如果設定autocommit 為 off,意味著以後每條SQL 都會處於一個事務中,相當於每條SQL執行前 都執行                      start transaction

mysql預設事務是自動提交的,一條sql是一個事務

當手動開啟事務後,資料庫預設的事務的自動提交暫時失效手動開啟事務:start transaction

提交事務:commit

提交事務到開啟事務之間的所有的sql語句都生效

回滾事務:rollback

從回滾事務到開啟事務之間的所有的sql操作都無效

jdbc的API的事務控制

通過Connection物件可以控制事務

jdbc中想控制事務其實是控制jdbc的更新資料庫的API方法---executeUpdate

開啟事務:connection.setAutoCommit(false);

提交事務:connection.commit();

回滾事務:connection.rollback();

官方的介紹事務特性

事務的四大特性簡稱ACID(Atomicity Consistency Isolation Durability),分別是:

l 原子性:原子性對應的英文是Atomicity,即表示事務中所有操作是不可再分割的原子單位。事務中所有操作要麼全部執行成功,要麼全部執行失敗;

l 一致性:一致性對應的英文是Consistency,事務執行後,資料庫狀態與其它業務規則保持一致。例如轉賬業務,無論事務執行成功與否,參與轉賬的兩個賬號餘額之和應該是不變的;

l 隔離性:隔離性對應的英文是Isolation,是指在併發操作中,不同事務之間應該隔離開來,使每個併發中的事務不會相互干擾;

l 永續性:永續性對應的英文是Durability,指的是一旦事務提交成功,事務中所有的資料操作都必須被持久化到資料庫中,即使提交事務後,資料庫馬上崩潰,在資料庫重啟時,也必須能保證通過某種機制恢復資料。

不同的事務,其一致性的表現形式是不同的,事務的其他三大特性其實都是為了事務的一致性服務的。

事務的四大特性概括

原子性:資料庫的操作的最小的單位就是事務

一致性:一個事務中的多個操作的結果資料是一致的,同時成功和同時失敗

隔離性:多個事務之間的操作互不影響

永續性:當一個事務提交後,更新操作才持久化到磁碟上

事務的隔離級別與問題

不考慮隔離性產生的問題介紹

n 髒讀 一個事務讀取到了另一個事務未提交資料.

n 不可重複讀 一個事務內,兩次讀取到的資料不一致.(update)

n 虛讀(幻讀兩次讀取的資料不一致(insert)

事務的四種隔離級別介紹

資料庫內部定義了四種隔離級別,用於解決三種隔離問題

u 1 Serializable:可避免髒讀、不可重複讀、虛讀情況的發生。(序列化)

u 2 Repeatable read:可避免髒讀、不可重複讀情況的發生。(可重複讀)不可以避免虛讀

u 3 Read committed:可避免髒讀情況發生(讀已提交)

u 4 Read uncommitted:最低級別,以上情況均無法保證。(讀未提交)

mysql資料庫預設的事務隔離級別-----repeatable read級別.

oracle資料預設的事務隔離級別 ----read committed

設定事務隔離級別

n mysql中設定

資料庫預設有事務的隔離級別,mysql 中檢視與修改事務的隔離級別

u set session transaction isolation level 設定事務隔離級別

u select @@tx_isolation 查詢當前事務隔離級別

n jdbc中設定事務隔離級別

java.sql.Connection介面中提供

u setTransactionIsolation(int level) ;

引數可以取 Connection 常量之一:

Connection.TRANSACTION_READ_UNCOMMITTED

Connection.TRANSACTION_READ_COMMITTED

Connection.TRANSACTION_REPEATABLE_READ

Connection.TRANSACTION_SERIALIZABLE

(注意,不能使用 Connection.TRANSACTION_NONE,因為它指定了不受支援的事務。)

事務的丟失更新問題(Lost Update)

兩個或多個事務更新同一行,但這些事務彼此之間都不知道其他事務進行的修改,因此第二個更改覆蓋了第一個修改。 如何解決事務的丟失更新問題? 解決事務的丟失更新可以採用兩種方式

方式一:悲觀鎖

       悲觀鎖(假設丟失更新一定會發生)——利用資料庫內部鎖機制,管理事務提供的鎖機制       (1)共享鎖                 select  * from table lock  in  share mode  (讀鎖、共享鎖)       (2)排它鎖                 select  * from table for update  (寫鎖、排它鎖)  悲觀鎖詳解:          MySQL鎖機制分為表級鎖(例如  事務隔離級別中的Serializable)和行級鎖。 共享鎖又稱為讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於同一資料可以共享一把鎖,都能訪問到資料,但是隻能讀不能修改。       排它鎖又稱為寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其他鎖並存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能再獲取該行的其他鎖,包括共享鎖和排他鎖,但是獲取排他鎖的事務是可以對資料就行讀取和修改。   其實共享鎖就是多個事務只能讀資料不能改資料。對於排它鎖而言,當排它鎖鎖住一行資料後 並不是說其他事務不能讀取和修改這行資料,排它鎖指的是一個事務在一行資料上加上排它鎖後,其他事務不能在其上面加任何其他的鎖。MySQL引擎預設的修改資料語句(update、insert、delete)都會自動給涉及到的資料加上排它鎖,select 語句預設不會加任何鎖型別,如果加排它鎖可以使用select ... for update語句,加共享鎖可以使用select ... lock in share mode語句。所以加過排它鎖的資料行在其他事務中是不能修改資料的,也不能通過 for  update 和lock in share mode鎖的方式查詢資料,但是可以直接通過select ...  from ... 查詢資料,因為普通查詢預設沒有任何鎖機制。

注意:事務控制必須在service層

連線池

  • 連線池:就是建立一個容器,用於裝入多個Connection物件,在使用連線物件時,從容器中獲取一個Connection, 使用完成後,在將這個Connection重新裝入到容器中。這個容器就是連線池。(DataSource)也叫做資料來源.
  • 作用:我們可以通過連線池獲取連線物件。

  • 優點:

    • 節省建立連線與釋放連線造成的效能消耗 —- 連線池中連線起到複用的作用 ,提高程式效能
  • 應用程式直接獲取連結 

  • 應用程式直接獲取連結的缺點

    • 使用者每次請求都需要向資料庫獲得連結,而資料庫建立連線通常需要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,資料庫伺服器就需要建立10萬次連線,極大的浪費資料庫的資源,並且極易造成資料庫伺服器記憶體溢位、拓機。

資料庫連線池編寫原理分析

  • 編寫連線池需實現javax.sql.DataSource介面。DataSource介面中定義了兩個過載的getConnection方法:

    • Connection getConnection()
    • Connection getConnection(String username, String password)
  • 實現DataSource介面,並實現連線池功能的步驟:

    • 在DataSource建構函式中批量建立與資料庫的連線,並把建立的連線儲存到一個集合物件中
    • 實現getConnection方法,讓getConnection方法每次呼叫時,從集合物件中取一個Connection返回給使用者。
    • 當用戶使用完Connection,呼叫Connection.close()方法時,Collection物件應保證將自己返回到連線池的集合物件中,而不要把conn還給資料庫。
  • 原來由jdbcUtil建立連線,現在由dataSource建立連線,為實現不和具體資料為繫結,因此datasource也應採用配置檔案的方法獲得連線。

自定義連線池

  • 1.建立一個MyDataSource類,在這個類中建立一個LinkedList<Connection>
  • 2.在其構造方法中初始化List集合,並向其中裝入5個Connection物件。
  • 3.建立一個public Connection getConnection();從List集合中獲取一個連線物件返回.
  • 4.建立一個 public void readd(Connection) 這個方法是將使用完成後的Connection物件重新裝入到List集合中.

  • 程式碼問題:

  • 1.連線池的建立是有標準的.

    • javax.sql包下定義了一個介面 DataSource
    • 所有的連線池必須實現javax.sql.DataSource介面
  • 2.我們操作時,要使用標準,怎樣可以讓 con.close()它不是銷燬,而是將其重新裝入到連線池.

    • 要解決這個問題,其本質就是將Connection中的close()方法的行為改變。
    • 怎樣可以改變一個方法的行為(對方法功能進行增強) 
      • 1.繼承
      • 2.裝飾模式 
        • 1.裝飾類與被裝飾類要實現同一個介面或繼承同一個父類
        • 2.在裝飾類中持有一個被裝飾類引用
        • 3.對方法進行功能增強。
      • 3.動態代理 
        • 可以對行為增強
        • Proxy.newProxyInstance(ClassLoacer ,Class[],InvocationHandler);
    • 結論:Connection物件如果是從連線池中獲取到的,那麼它的close方法的行為已經改變了,不在是銷燬,而是重新裝入到連線池。

    • 1.連線池必須實現javax.sql.DataSource介面。

    • 2.要通過連線池獲取連線物件 DataSource介面中有一個 getConnection方法.
    • 3.將Connection重新裝入到連線池 使用Connection的close()方法。

開源連線池

  • 現在很多WEB伺服器(Weblogic, WebSphere, Tomcat)都提供了DataSoruce的實現,即連線池的實現。通常我們把DataSource的實現,按其英文含義稱之為資料來源,資料來源中都包含了資料庫連線池的實現。
  • 也有一些開源組織提供了資料來源的獨立實現: 
    • DBCP 資料庫連線池
    • C3P0 資料庫連線池
    • Apache Tomcat內建的連線池(apache dbcp)
  • 實際應用時不需要編寫連線資料庫程式碼,直接從資料來源獲得資料庫的連線。程式設計師程式設計時也應儘量使用這些資料來源的實現,以提升程式的資料庫訪問效能。

DBCP資料來源(瞭解)

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

C3P0資料來源(必會)

  • C3P0是一個開源的JDBC連線池,它實現了資料來源和JNDI繫結,支援JDBC3規範和JDBC2的標準擴充套件。
  • 目前使用它的開源專案有Hibernate,Spring等。

  • c3p0與dbcp區別

    • dbcp沒有自動回收空閒連線的功能
    • c3p0有自動回收空閒連線功能
  • 1.導包

    • c3p0-0.9.1.2.jar
  • 1.手動使用

ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver");
cpds.setJdbcUrl("jdbc:mysql:///day18");
cpds.setUser("root");
cpds.setPassword("abc");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 2.自動(使用配置檔案) 
    • c3p0的配置檔案可以是properties也可以是xml。
    • c3p0的配置檔案如果名稱叫做 c3p0.properties or c3p0-config.xml 並且放置在classpath路徑下(對於web應用就是classes目錄)那麼c3p0會自動查詢。
    • 注意:我們其時只需要將配置檔案放置在src下就可以。
    • 使用: 
      • ComboPooledDataSource cpds = new ComboPooledDataSource();
      • 它會在指定的目錄下查詢指定名稱的配置檔案,並將其中內容載入。
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///day18</property>
        <property name="user">root</property>
        <property name="password">abc</property>
    </default-config>

</c3p0-config>


    @Test
    public void test2() throws PropertyVetoException, SQLException {
        ComboPooledDataSource cpds = new ComboPooledDataSource();

        // 得到一個Connection
        Connection con = cpds.getConnection();

        ResultSet rs = con.createStatement().executeQuery(
                "select * from account");

        while (rs.next()) {

            System.out.println(rs.getInt("id") + "  " + rs.getString("name"));
        }

        rs.close();
        con.close(); // 將Connection物件重新裝入到連線池.

        // String path = this.getClass().getResource("/").getPath();
        // System.out.println(path);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

配置Tomcat資料來源

  • tomcat伺服器內建連線池 (使用Apache DBCP)
  • 配置tomcat 內建連線池,通過JNDI方式 去訪問tomcat的內建連線池
JNDI技術簡介
  • JNDI(Java Naming and Directory Interface),Java**命名和目錄介面,它對應於**J2SE中的javax.naming包。是JavaEE一項技術,允許將一個Java物件繫結到一個JNDI容器(tomcat)中,並且為物件指定一個名稱,通過javax.naming 包 Context 對JNDI 容器中繫結的物件進行查詢,通過指定名稱找到繫結Java物件。

  • 這套API的主要作用在於:它可以把Java物件放在一個容器中(支援JNDI容器 Tomcat),併為容器中的java物件取一個名稱,以後程式想獲得Java物件,只需通過名稱檢索即可。

  • 其核心API為Context,它代表JNDI容器,其lookup方法為檢索容器中對應名稱的物件。
配置操作步驟
  • 1、配置使用tomcat 內建連線池 配置<context> 元素
  • context元素有三種常見配置位置 
    • 1) tomcat/conf/context.xml 所有虛擬主機,所有工程都可以訪問該連線池
    • 2) tomcat/conf/Catalina/localhost/context.xml 當前虛擬主機(localhost)下所有工程都可以使用該連線池
    • 3) 當前工程/META-INF/context.xml 只有當前工程可以訪問該連線池
<Context>
  <Resource name="jdbc/EmployeeDB" auth="Container"
            type="javax.sql.DataSource" username="root" password="abc"
            driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql:///day14"
            maxActive="8" maxIdle="4"/>
</Context>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 必須先將mysql驅動jar包 複製tomcat/lib下
  • 在tomcat啟動伺服器時,建立連線池物件,繫結 jdbc/EmployeeDB 指定名稱上

  • 2、通過執行在JNDI容器內部的程式(Servlet/JSP)去訪問tomcat內建連線池

public class DataSourceServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        try {
            Context context = new InitialContext();
            Context envCtx = (Context) context.lookup("java:comp/env"); // 固定路徑
            DataSource datasource = (DataSource) envCtx
                    .lookup("jdbc/EmployeeDB");//自己定義的資料庫名字

            Connection con = datasource.getConnection();
            ResultSet rs = con.createStatement().executeQuery(
                    "select * from account");

            while (rs.next()) {
                System.out.println(rs.getInt("id") + "  "
                        + rs.getString("name"));
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        doGet(request, response);
    }

}