事務&數據庫連接池


l  事務

l  連接池

l  ThreadLocal

l  BaseServlet自定義Servlet父類(只要求會用,不要求會寫)

l  DBUtils à commons-dbutils

事務

l  事務的四大特性:ACID;

l  mysql中操作事務

l  jdbc中操作事務

事務概述

為了方便演示事務,我們需要創建一個account表:

CREATE TABLE account(

         id INT PRIMARY KEY AUTO_INCREMENT,

         NAME VARCHAR(30),

         balance NUMERIC(10.2)

);

INSERT INTO account(NAME,balance) VALUES('zs', 100000);

INSERT INTO account(NAME,balance) VALUES('ls', 100000);

INSERT INTO account(NAME,balance) VALUES('ww', 100000);

SELECT * FROM account;

 

1 什么是事務

銀行轉賬!張三轉10000塊到李四的賬戶,這其實需要兩條SQL語句:

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

l  給李四的賬戶加上10000元。

如果在第一條SQL語句執行成功后,在執行第二條SQL語句之前,程序被中斷了(可能是拋出了某個異常,也可能是其他什么原因),那么李四的賬戶沒有加上10000元,而張三卻減去了10000元。這肯定是不行的!

你現在可能已經知道什么是事務了吧!事務中的多個操作,要么完全成功,要么完全失敗!不可能存在成功一半的情況!也就是說給張三的賬戶減去10000元如果成功了,那么給李四的賬戶加上10000元的操作也必須是成功的;否則給張三減去10000元,以及給李四加上10000元都是失敗的!

 

2 事務的四大特性(ACID)

  面試!

事務的四大特性是:

l  原子性(Atomicity):事務中所有操作是不可再分割的原子單位。事務中所有操作要么全部執行成功,要么全部執行失敗。

l  一致性[c1] (Consistency):事務執行后,數據庫狀態與其它業務規則保持一致。如轉賬業務,無論事務執行成功與否,參與轉賬的兩個賬號余額之和應該是不變的。

l  隔離性(Isolation):隔離性是指在並發操作中,不同事務之間應該隔離開來,使每個並發中的事務不會相互干擾。

l  持久性(Durability):一旦事務提交成功,事務中所有的數據操作都必須被持久化到數據庫中,即使提交事務后,數據庫馬上崩潰,在數據庫重啟時,也必須能保證通過某種機制恢復數據。

 

3 MySQL中的事務

在默認情況下,MySQL每執行一條SQL語句,都是一個單獨的事務。如果需要在一個事務中包含多條SQL語句,那么需要開啟事務和結束事務。

l  開啟事務:start transaction

l  結束事務:commitrollback

在執行SQL語句之前,先執行strat transaction,這就開啟了一個事務(事務的起點),然后可以去執行多條SQL語句,最后要結束事務,commit表示提交,即事務中的多條SQL語句所做出的影響會持久化到數據庫中。或者rollback,表示回滾,即回滾到事務的起點,之前做的所有操作都被撤消了!

下面演示zs給li轉賬10000元的示例:

START TRANSACTION;

UPDATE account SET balance=balance-10000 WHERE id=1;

UPDATE account SET balance=balance+10000 WHERE id=2;

ROLLBACK[崔2] ;

START TRANSACTION;

UPDATE account SET balance=balance-10000 WHERE id=1;

UPDATE account SET balance=balance+10000 WHERE id=2;

COMMIT[崔3] ;

START TRANSACTION;

UPDATE account SET balance=balance-10000 WHERE id=1;

UPDATE account SET balance=balance+10000 WHERE id=2;

quit[崔4] ;

 

JDBC事務

在jdbc中處理事務,都是通過Connection完成的!

同一事務中所有的操作,都在使用同一個Connection對象!

 

1 JDBC中的事務

Connection的三個方法與事務相關:

l  setAutoCommit(boolean):設置是否為自動提交事務,如果true(默認值就是true)表示自動提交,也就是每條執行的SQL語句都是一個單獨的事務,如果設置false,那么就相當於開啟了事務了;con.setAutoCommit(false)表示開啟事務!!!

l  commit():提交結束事務;con.commit();表示提交事務

l  rollback():回滾結束事務。con.rollback();表示回滾事務

 

jdbc處理事務的代碼格式:

try {

  con.setAutoCommit(false);//開啟事務…

  ….

  …

  con.commit();//try的最后提交事務

} catch() {

  con.rollback();//回滾事務

}

 

 

    public void transfer(boolean b) {

       Connection con = null;

       PreparedStatement pstmt = null;

      

       try {

           con = JdbcUtils.getConnection();

           //手動提交

           con.setAutoCommit(false);[崔5] 

          

           String sql = "update account set balance=balance+? where id=?";

           pstmt = con.prepareStatement(sql);

          

           //操作

           pstmt.setDouble(1, -10000);

           pstmt.setInt(2, 1);

           pstmt.executeUpdate();

          

           // 在兩個操作中拋出異常

           if(b) {

              throw new Exception();

           }

[崔6]          

           pstmt.setDouble(1, 10000);

           pstmt.setInt(2, 2);

           pstmt.executeUpdate();

          

           //提交事務

           con.commit();[崔7] 

       } catch(Exception e) {

           //回滾事務

           if(con != null) {

              try {

                  con.rollback();[崔8] 

              } catch(SQLException ex) {}

           }

           throw new RuntimeException(e);

       } finally {

           //關閉

           JdbcUtils.close(con, pstmt);

       }

    }

 

2 保存點[c9] (了解)

保存點是JDBC3.0的東西!當要求數據庫服務器支持保存點方式的回滾。

校驗數據庫服務器是否支持保存點!

boolean b = con.getMetaData().supportsSavepoints();

 

保存點的作用是允許事務回滾到指定的保存點位置。在事務中設置好保存點,然后回滾時可以選擇回滾到指定的保存點,而不是回滾整個事務!注意,回滾到指定保存點並沒有結束事務!!!只有回滾了整個事務才算是結束事務了!

Connection類的設置保存點,以及回滾到指定保存點方法:

l  設置保存點:Savepoint setSavepoint();

l  回滾到指定保存點:void rollback(Savepoint)。

 

    /*

     * 李四對張三說,如果你給我轉1W,我就給你轉100W。

     * ==========================================

     *

     * 張三給李四轉1W(張三減去1W,李四加上1W)

     * 設置保存點!

     * 李四給張三轉100W(李四減去100W,張三加上100W)

     * 查看李四余額為負數,那么回滾到保存點。

     * 提交事務

     */

    @Test

    public void fun() {

       Connection con = null;

       PreparedStatement pstmt = null;

      

       try {

           con = JdbcUtils.getConnection();

           //手動提交

           con.setAutoCommit(false);[崔10] 

          

           String sql = "update account set balance=balance+? where name=?";

           pstmt = con.prepareStatement(sql);

          

           //操作1(張三減去1W)

           pstmt.setDouble(1, -10000);

           pstmt.setString(2, "zs");

           pstmt.executeUpdate();

[崔11]          

           //操作2(李四加上1W)

           pstmt.setDouble(1, 10000);

           pstmt.setString(2, "ls");

           pstmt.executeUpdate();

[崔12]          

           // 設置保存點

           Savepoint sp = con.setSavepoint();[崔13] 

          

           //操作3(李四減去100W)

           pstmt.setDouble(1, -1000000);

           pstmt.setString(2, "ls");

           pstmt.executeUpdate();     

[崔14]          

           //操作4(張三加上100W)

           pstmt.setDouble(1, 1000000);

           pstmt.setString(2, "zs");

           pstmt.executeUpdate();

[崔15]          

           //操作5(查看李四余額)

           sql = "select balance from account where name=?";

           pstmt = con.prepareStatement(sql);

           pstmt.setString(1, "ls");

           ResultSet rs = pstmt.executeQuery();

           rs.next();

           double balance = rs.getDouble(1);

[崔16]       //如果李四余額為負數,那么回滾到指定保存點

           if(balance < 0) {

              con.rollback(sp);[崔17] 

              System.out.println("張三,你上當了!");

           }

          

           //提交事務

           con.commit()[崔18] ;

       } catch(Exception e) {

           //回滾事務

           if(con != null) {

              try {

                  con.rollback();

              } catch(SQLException ex) {}

           }

           throw new RuntimeException(e);

       } finally {

           //關閉

           JdbcUtils.close(con, pstmt);

       }

    }

 

事務隔離級別

 

1         事務的並發讀問題

l  臟讀[c19] :讀取到另一個事務未提交數據;

l  不可重復讀:兩次讀取不一致;

l  幻讀(虛讀):讀到另一事務已提交數據。

 

2 並發事務問題

因為並發事務導致的問題大致有5類,其中兩類是更新問題,三類是讀問題。

 

l  臟讀(dirty read):讀到另一個事務的未提交更新數據,即讀取到了臟數據;

l  不可重復讀(unrepeatable read):對同一記錄的兩次讀取不一致,因為另一事務對該記錄做了修改;

l  幻讀(虛讀)(phantom read):對同一張表的兩次查詢不一致,因為另一事務插入了一條記錄;

 

臟讀

事務1:張三給李四轉賬100元

事務2:李四查看自己的賬戶

 

l  t1:事務1:開始事務

l  t2:事務1:張三給李四轉賬100元

l  t3:事務2:開始事務

l  t4:事務2:李四查看自己的賬戶,看到賬戶多出100元(臟讀)

l  t5:事務2:提交事務

l  t6:事務1:回滾事務,回到轉賬之前的狀態

不可重復讀

事務1:酒店查看兩次1048號房間狀態

事務2:預訂1048號房間

 

l  t1:事務1:開始事務

l  t2:事務1:查看1048號房間狀態為空閑

l  t3:事務2:開始事務

l  t4:事務2:預定1048號房間

l  t5:事務2:提交事務

l  t6:事務1:再次查看1048號房間狀態為使用

l  t7:事務1:提交事務

對同一記錄的兩次查詢結果不一致!

 

幻讀

事務1:對酒店房間預訂記錄兩次統計

事務2:添加一條預訂房間記錄

 

l  t1:事務1:開始事務

l  t2:事務1:統計預訂記錄100條

l  t3:事務2:開始事務

l  t4:事務2:添加一條預訂房間記錄

l  t5:事務2:提交事務

l  t6:事務1:再次統計預訂記錄為101記錄

l  t7:事務1:提交

  對同一表的兩次查詢不一致!

 

不可重復讀和幻讀的區別:

l  不可重復讀是讀取到了另一事務的更新;

l  幻讀是讀取到了另一事務的插入(MySQL中無法測試到幻讀);

 

3 四大隔離級別

 

4個等級的事務隔離級別,在相同數據環境下,使用相同的輸入,執行相同的工作,根據不同的隔離級別,可以導致不同的結果。不同事務隔離級別能夠解決的數據並發問題的能力是不同的。

 

1 SERIALIZABLE(串行化)[c20] 

l  不會出現任何並發問題,因為它是對同一數據的訪問是串行的,非並發訪問的;

l  性能最差;

 

2 REPEATABLE READ[c21] (可重復讀)(MySQL)

l  防止臟讀和不可重復讀,不能處理幻讀問題;

l  性能比SERIALIZABLE好

 

3 READ COMMITTED[c22] (讀已提交數據)(Oracle)

l  防止臟讀,沒有處理不可重復讀,也沒有處理幻讀;

l  性能比REPEATABLE READ好

4 READ UNCOMMITTED[c23] (讀未提交數據)

l  可能出現任何事務並發問題

l  性能最好

 

MySQL的默認隔離級別為REPEATABLE READ,這是一個很不錯的選擇吧!

 

5 MySQL隔離級別

MySQL的默認隔離級別為Repeatable read,可以通過下面語句查看:

select @@tx_isolation

 

也可以通過下面語句來設置當前連接的隔離級別:

set transaction isolationlevel [4先1]

 

6 JDBC設置隔離級別

con. setTransactionIsolation(int level)

參數可選值如下:

l  Connection.TRANSACTION_READ_UNCOMMITTED;

l  Connection.TRANSACTION_READ_COMMITTED;

l  Connection.TRANSACTION_REPEATABLE_READ;

l  Connection.TRANSACTION_SERIALIZABLE。

 

事務總結:

l  事務的特性:ACID;

l  事務開始邊界與結束邊界:開始邊界(con.setAutoCommit(false)),結束邊界(con.commit()或con.rollback());

l  事務的隔離級別: READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。多個事務並發執行時才需要考慮並發事務。

 

數據庫連接池

池參數(所有池參數都有默認值)

初始大小:10個

最小空閑連接數:3個

增量:一次創建的最小單位(5個)

最大空閑連接數:12個

最大連接數:20個

最大的等待時間:1000毫秒

 

四大連接參數

連接池也是使用四大連接參數來完成創建連接對象!

 

實現的接口

連接池必須實現:javax.sql.DataSource接口!

 

連接池返回的Connection對象,它的close()方法與眾不同!調用它的close()不是關閉,而是把連接歸還給池!

 

數據庫連接池

 

1 數據庫連接池的概念

用池來管理Connection,這可以重復使用Connection。有了池,所以我們就不用自己來創建Connection,而是通過池來獲取Connection對象。當使用完Connection后,調用Connection的close()方法也不會真的關閉Connection,而是把Connection“歸還”給池。池就可以再利用這個Connection對象了。

 

 

2 JDBC數據庫連接池接口(DataSource)

  Java為數據庫連接池提供了公共的接口:javax.sql.DataSource,各個廠商可以讓自己的連接池實現這個接口。這樣應用程序可以方便的切換不同廠商的連接池!

 

3 自定義連接池(ItcastPool)

  分析:ItcastPool需要有一個List,用來保存連接對象。在ItcastPool的構造器中創建5個連接對象放到List中!當用人調用了ItcastPool的getConnection()時,那么就從List拿出一個返回。當List中沒有連接可用時,拋出異常。

  我們需要對Connection的close()方法進行增強,所以我們需要自定義ItcastConnection類,對Connection進行裝飾!即對close()方法進行增強。因為需要在調用close()方法時把連接“歸還”給池,所以ItcastConnection類需要擁有池對象的引用,並且池類還要提供“歸還”的方法。

 

ItcastPool.java

public class ItcastPool implements DataSource [崔24] {

    private static Properties props = new Properties();

    private List<Connection> list [崔25] = new ArrayList<Connection>();

    static [崔26] {

       InputStream in = ItcastPool.class.getClassLoader()

              .getResourceAsStream("dbconfig.properties");

       try {

           props.load(in);

           Class.forName(props.getProperty("driverClassName"));

       } catch (Exception e) {

           throw new RuntimeException(e);

       }

    }

 

    public ItcastPool[崔27] () throws SQLException {

       for (int i = 0; i < 5; i++) {

           Connection con = DriverManager.getConnection(

                  props.getProperty("url"), props.getProperty("username"),

                  props.getProperty("password"));[崔28] 

           ItcastConnection conWapper = new ItcastConnection(con, this);[崔29] 

           list.add(conWapper);[崔30] 

       }

    }

   

    public void add[崔31] (Connection con) {

       list.add(con);

    }

 

    public Connection getConnection[崔32] () throws SQLException {

       if(list.size() > 0) {

           return list.remove(0);

       }

       throw new SQLException("沒連接了");

    }

    ......[崔33] 

}

 

ItcastConnection.java

public class ItcastConnection extends ConnectionWrapper[崔34]  {

    private ItcastPool pool;

   

    public ItcastConnection(Connection con, ItcastPool pool) {

       super(con);

       this.pool = pool[崔35] ;

    }

 

    @Override

    public void close[崔36] () throws SQLException {

       pool.add(this);

    }

}

 

DBCP

 

1 什么是DBCP?

DBCP是Apache提供的一款開源免費的數據庫連接池!

Hibernate3.0之后不再對DBCP提供支持!因為Hibernate聲明DBCP有致命的缺欠!DBCP因為Hibernate的這一毀謗很是生氣,並且說自己沒有缺欠。

 

2 DBCP的使用

    public void fun1() throws SQLException {

       BasicDataSource ds = new BasicDataSource();

       ds.setUsername("root");

       ds.setPassword("123");

       ds.setUrl("jdbc:mysql://localhost:3306/mydb1");

       ds.setDriverClassName("com.mysql.jdbc.Driver");

[崔37]      

       ds.setMaxActive(20);[崔38] 

       ds.setMaxIdle(10);[崔39] 

       ds.setInitialSize(10)[崔40] ;

       ds.setMinIdle(2)[崔41] ;

       ds.setMaxWait(1000)[崔42] ;

      

       Connection con = ds.getConnection();

       System.out.println(con.getClass().getName());

       con.close()[崔43] ;

    }

 

3 DBCP的配置信息

下面是對DBCP的配置介紹:

#基本配置

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

 

C3P0

 

1 C3P0簡介

  C3P0也是開源免費的連接池!C3P0被很多人看好!

 

2 C3P0的使用

  C3P0中池類是:ComboPooledDataSource。

    public void fun1() throws PropertyVetoException, SQLException {

       ComboPooledDataSource ds = new ComboPooledDataSource();

       ds.setJdbcUrl("jdbc:mysql://localhost:3306/mydb1");

       ds.setUser("root");

       ds.setPassword("123");

       ds.setDriverClass("com.mysql.jdbc.Driver");

[崔44]      

       ds.setAcquireIncrement(5)[崔45] ;

       ds.setInitialPoolSize(20)[崔46] ;

       ds.setMinPoolSize(2)[崔47] ;

       ds.setMaxPoolSize(50)[崔48] ;

      

       Connection con = ds.getConnection();

       System.out.println(con);

       con.close();

    }

 

配置文件要求:

l  文件名稱:必須叫c3p0-config.xml

l  文件位置:必須在src下

 

 

c3p0也可以指定配置文件,而且配置文件可以是properties,也可騍xml的。當然xml的高級一些了。但是c3p0的配置文件名必須為c3p0-config.xml,並且必須放在類路徑下。

<?xml version="1.0" encoding="UTF-8"?>

<c3p0-config>

    <default-config>[崔49] 

       <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>

    </default-config>

    <named-config name="oracle-config">[崔50] 

       <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>

 

  c3p0的配置文件中可以配置多個連接信息,可以給每個配置起個名字,這樣可以方便的通過配置名稱來切換配置信息。上面文件中默認配置為mysql的配置,名為oracle-config的配置也是mysql的配置,呵呵。

    public void fun2() throws PropertyVetoException, SQLException {

       ComboPooledDataSource ds = new ComboPooledDataSource();[崔51] 

       Connection con = ds.getConnection();

       System.out.println(con);

       con.close();

    }

    public void fun2() throws PropertyVetoException, SQLException {

       ComboPooledDataSource ds = new ComboPooledDataSource("orcale-config")[崔52] ;

       Connection con = ds.getConnection();

       System.out.println(con);

       con.close();

    }

 

Tomcat配置連接池

 

1 Tomcat配置JNDI資源

JNDI(Java Naming and Directory Interface),Java命名和目錄接口。JNDI的作用就是:在服務器上配置資源,然后通過統一的方式來獲取配置的資源。

我們這里要配置的資源當然是連接池了,這樣項目中就可以通過統一的方式來獲取連接池對象了。

下圖是Tomcat文檔提供的:

 

 

配置JNDI資源需要到<Context>元素中配置<Resource>子元素:

l  name:指定資源的名稱,這個名稱可以隨便給,在獲取資源時需要這個名稱;

l  factory:用來創建資源的工廠,這個值基本上是固定的,不用修改;

l  type:資源的類型,我們要給出的類型當然是我們連接池的類型了;

l  bar:表示資源的屬性,如果資源存在名為bar的屬性,那么就配置bar的值。對於DBCP連接池而言,你需要配置的不是bar,因為它沒有bar這個屬性,而是應該去配置url、username等屬性。

 

<Context> 

  <Resource name="mydbcp"

                            type="org.apache.tomcat.dbcp.dbcp.BasicDataSource"

                            factory="org.apache.naming.factory.BeanFactory"

                            username="root"

                            password="123"

                            driverClassName="com.mysql.jdbc.Driver"   

                            url="jdbc:mysql://127.0.0.1/mydb1"

                            maxIdle="3"

                            maxWait="5000"

                            maxActive="5"

                            initialSize="3"/>

</Context> 

<Context> 

  <Resource name="myc3p0"

                            type="com.mchange.v2.c3p0.ComboPooledDataSource"

                            factory="org.apache.naming.factory.BeanFactory"

                            user="root"

                            password="123"

                            classDriver="com.mysql.jdbc.Driver"   

                            jdbcUrl="jdbc:mysql://127.0.0.1/mydb1"

                            maxPoolSize="20"

                            minPoolSize ="5"

                            initialPoolSize="10"

                            acquireIncrement="2"/>

</Context> 

 

2 獲取資源

  配置資源的目的當然是為了獲取資源了。只要你啟動了Tomcat,那么就可以在項目中任何類中通過JNDI獲取資源的方式來獲取資源了。

下圖是Tomcat文檔提供的,與上面Tomcat文檔提供的配置資源是對應的。

 

 

獲取資源:

l  Context:javax.naming.Context;

l  InitialContext:javax.naming.InitialContext;

l  lookup(String):獲取資源的方法,其中”java:comp/env”是資源的入口(這是固定的名稱),獲取過來的還是一個Context,這說明需要在獲取到的Context上進一步進行獲取。”bean/MyBeanFactory”對應<Resource>中配置的name值,這回獲取的就是資源對象了。

 

            Context cxt = new InitialContext();

            DataSource ds = (DataSource)cxt.lookup("java:/comp/env/mydbcp");

            Connection con = ds.getConnection();

            System.out.println(con);

            con.close();

            Context cxt = new InitialContext();

            Context envCxt = (Context)cxt.lookup("java:/comp/env");

            DataSource ds = (DataSource)env.lookup("mydbcp");

            Connection con = ds.getConnection();

            System.out.println(con);

            con.close();

 

  上面兩種方式是相同的效果。

 

修改JdbcUtils

因為已經學習了連接池,那么JdbcUtils的獲取連接對象的方法也要修改一下了。

JdbcUtils.java

public class JdbcUtils {

    private static DataSource dataSource = new ComboPooledDataSource();

 

    public static DataSource getDataSource() {

       return dataSource;

    }

 

    public static Connection getConnection() {

       try {

           return dataSource.getConnection();

       } catch (Exception e) {

           throw new RuntimeException(e);

       }

    }

}

 

ThreadLocal

 

Thread à 人類

Runnable à 任務類

 

 

key

value

thread1

aaa

thread2

bbb

thread3

ccc

 

1 ThreadLocal API

ThreadLocal類只有三個方法:

l  void set(T value):保存值;

l  T get():獲取值;

l  void remove():移除值。

 

2 ThreadLocal的內部是Map

ThreadLocal內部其實是個Map來保存數據。雖然在使用ThreadLocal時只給出了值,沒有給出鍵,其實它內部使用了當前線程做為鍵。

class MyThreadLocal<T> {

   private Map<Thread,T> map = new HashMap<Thread,T>();

   public void set(T value) {

      map.put(Thread.currentThread(), value);

   }

  

   public void remove() {

      map.remove(Thread.currentThread());

   }

  

   public T get() {

      return map.get(Thread.currentThread());

   }

}

 

 

 

BaseServlet

 

1 BaseServlet的作用

 

在開始客戶管理系統之前,我們先寫一個工具類:BaseServlet。

我們知道,寫一個項目可能會出現N多個Servlet,而且一般一個Servlet只有一個方法(doGet或doPost),如果項目大一些,那么Servlet的數量就會很驚人。

為了避免Servlet的“膨脹”,我們寫一個BaseServlet。它的作用是讓一個Servlet可以處理多種不同的請求。不同的請求調用Servlet的不同方法。我們寫好了BaseServlet后,讓其他Servlet繼承BaseServlet,例如CustomerServlet繼承BaseServlet,然后在CustomerServlet中提供add()、update()、delete()等方法,每個方法對應不同的請求。

 

 

2 BaseServlet分析

我們知道,Servlet中處理請求的方法是service()方法,這說明我們需要讓service()方法去調用其他方法。例如調用add()、mod()、del()、all()等方法!具體調用哪個方法需要在請求中給出方法名稱!然后service()方法通過方法名稱來調用指定的方法。

無論是點擊超鏈接,還是提交表單,請求中必須要有method參數,這個參數的值就是要請求的方法名稱,這樣BaseServlet的service()才能通過方法名稱來調用目標方法。例如某個鏈接如下:

<a href=”/xxx/CustomerServlet?method=add”>添加客戶</a>

 

 

3 BaseServlet代碼

public class BaseServlet extends HttpServlet {

    /*

     * 它會根據請求中的m,來決定調用本類的哪個方法

     */

    protected void service(HttpServletRequest req, HttpServletResponse res)

           throws ServletException, IOException {

       req.setCharacterEncoding("UTF-8");

       res.setContentType("text/html;charset=utf-8");

 

       // 例如:http://localhost:8080/demo1/xxx?m=add

       String methodName = req.getParameter("method");// 它是一個方法名稱

      

       // 當沒用指定要調用的方法時,那么默認請求的是execute()方法。

       if(methodName == null || methodName.isEmpty()) {

           methodName = "execute";

       }

       Class c = this.getClass();

       try {

           // 通過方法名稱獲取方法的反射對象

           Method m = c.getMethod(methodName, HttpServletRequest.class,

                  HttpServletResponse.class);

           // 反射方法目標方法,也就是說,如果methodName為add,那么就調用add方法。

           String result = (String) m.invoke(this, req, res);

           // 通過返回值完成請求轉發

           if(result != null && !result.isEmpty()) {

              req.getRequestDispatcher(result).forward(req, res);

           }

       } catch (Exception e) {

           throw new ServletException(e);

       }

    }

}

 

DBUtils

 

1 DBUtils簡介

DBUtils是Apache Commons組件中的一員,開源免費!

DBUtils是對JDBC的簡單封裝,但是它還是被很多公司使用!

DBUtils的Jar包:dbutils.jar

 

2 DBUtils主要類

l  DbUtils:都是靜態方法,一系列的close()方法;

l  QueryRunner:

  • update():執行insert、update、delete;
  • query():執行select語句;
  • batch():執行批處理。

 

3 QueryRunner之更新

QueryRunner的update()方法可以用來執行insert、update、delete語句。

  1. 創建QueryRunner

構造器:QueryRunner();

 

  1. update()方法

int update(Connection con, String sql, Object… params)

 

    @Test

    public void fun1() throws SQLException {

       QueryRunner qr = new QueryRunner();

       String sql = "insert into user values(?,?,?)";

       qr.update(JdbcUtils.getConnection(), sql, "u1", "zhangSan", "123");

    }

 

  還有另一種方式來使用QueryRunner

  1. 創建QueryRunner

構造器:QueryRunner(DataSource)

 

  1. update()方法

int update(String sql, Object… params)

 

這種方式在創建QueryRunner時傳遞了連接池對象,那么在調用update()方法時就不用再傳遞Connection了。

    @Test

    public void fun2() throws SQLException {

       QueryRunner qr = new QueryRunner(JdbcUtils.getDataSource());

       String sql = "insert into user values(?,?,?)";

       qr.update(sql, "u1", "zhangSan", "123");

    }

 

4 ResultSetHandler

我們知道在執行select語句之后得到的是ResultSet,然后我們還需要對ResultSet進行轉換,得到最終我們想要的數據。你可以希望把ResultSet的數據放到一個List中,也可能想把數據放到一個Map中,或是一個Bean中。

DBUtils提供了一個接口ResultSetHandler,它就是用來ResultSet轉換成目標類型的工具。你可以自己去實現這個接口,把ResultSet轉換成你想要的類型。

DBUtils提供了很多個ResultSetHandler接口的實現,這些實現已經基本夠用了,我們通常不用自己去實現ResultSet接口了。

l  MapHandler:單行處理器!把結果集轉換成Map<String,Object>,其中列名為鍵!

l  MapListHandler:多行處理器!把結果集轉換成List<Map<String,Object>>;

l  BeanHandler:單行處理器!把結果集轉換成Bean,該處理器需要Class參數,即Bean的類型;

l  BeanListHandler:多行處理器!把結果集轉換成List<Bean>;

l  ColumnListHandler:多行單列處理器!把結果集轉換成List<Object>,使用ColumnListHandler時需要指定某一列的名稱或編號,例如:new ColumListHandler(“name”)表示把name列的數據放到List中。

l  ScalarHandler:單行單列處理器!把結果集轉換成Object。一般用於聚集查詢,例如select count(*) from tab_student。

 

Map處理器

 

 

Bean處理器

 

 

Column處理器

 

Scalar處理器

 

 

5 QueryRunner之查詢

QueryRunner的查詢方法是:

public <T> T query(String sql, ResultSetHandler<T> rh, Object… params)

public <T> T query(Connection con, String sql, ResultSetHandler<T> rh, Object… params)

query()方法會通過sql語句和params查詢出ResultSet,然后通過rh把ResultSet轉換成對應的類型再返回。

 

    @Test

    public void fun1() throws SQLException {

       DataSource ds = JdbcUtils.getDataSource();

       QueryRunner qr = new QueryRunner(ds);

       String sql = "select * from tab_student where number=?";

       Map<String,Object> map = qr.query(sql, new MapHandler()[崔53] , "S_2000");

       System.out.println(map);

    }

   

    @Test

    public void fun2() throws SQLException {

       DataSource ds = JdbcUtils.getDataSource();

       QueryRunner qr = new QueryRunner(ds);

       String sql = "select * from tab_student";

       List<Map<String,Object>> list = qr.query(sql, new MapListHandler()[崔54] );

       for(Map<String,Object> map : list) {

           System.out.println(map);

       }

    }

   

    @Test

    public void fun3() throws SQLException {

       DataSource ds = JdbcUtils.getDataSource();

       QueryRunner qr = new QueryRunner(ds);

       String sql = "select * from tab_student where number=?";

       Student stu = qr.query(sql, new BeanHandler<Student>(Student.class)[崔55] , "S_2000");

       System.out.println(stu);

    }

   

    @Test

    public void fun4() throws SQLException {

       DataSource ds = JdbcUtils.getDataSource();

       QueryRunner qr = new QueryRunner(ds);

       String sql = "select * from tab_student";

       List<Student> list = qr.query(sql, new BeanListHandler<Student>(Student.class))[崔56] ;

       for(Student stu : list) {

           System.out.println(stu);

       }

    }

   

    @Test

    public void fun5() throws SQLException {

       DataSource ds = JdbcUtils.getDataSource();

       QueryRunner qr = new QueryRunner(ds);

       String sql = "select * from tab_student";

       List<Object> list = qr.query(sql, new ColumnListHandler("name"))[崔57] ;

       for(Object s : list) {

           System.out.println(s);

       }

    }

   

    @Test

    public void fun6() throws SQLException {

       DataSource ds = JdbcUtils.getDataSource();

       QueryRunner qr = new QueryRunner(ds);

       String sql = "select count(*) from tab_student";

       Number number = (Number)qr.query(sql, new ScalarHandler()[崔58] );

       int cnt = number.intValue()[崔59] ;

       System.out.println(cnt);

    }

 

5 QueryRunner之批處理

QueryRunner還提供了批處理方法:batch()。

我們更新一行記錄時需要指定一個Object[]為參數,如果是批處理,那么就要指定Object[][]為參數了。即多個Object[]就是Object[][]了,其中每個Object[]對應一行記錄:

@Test

public void fun10() throws SQLException {

    DataSource ds = JdbcUtils.getDataSource();

    QueryRunner qr = new QueryRunner(ds);

    String sql = "insert into tab_student values(?,?,?,?)";

    Object[][] params = new Object[10][];[崔60] //表示 要插入10行記錄

    for(int i = 0; i < params.length; i++) {

       params[i] = new Object[]{"S_300" + i, "name" + i, 30 + i, i%2==0?"男":"女"};

    }

    qr.batch[崔61] (sql, params);

}

 


 [c1]其他特性都是為了這一特性服務的。

 [崔2]回滾結束,事務執行失敗

 [崔3]提交結束,事務執行成功

 [崔4]退出,MySQL會自動回滾事務。

 [崔5]設置為手動提交事務,即開啟了事務。

 [崔6]如果出現了異常就回滾結束事務

 [崔7]當兩個操作都執行完了,提交結束事務。

 [崔8]當出現異常時,回滾事務。

 [c9]Spring中的七種事務的傳播機制

 [崔10]開始事務

 [崔11]給張三減1萬

 [崔12]給李四加1萬

 [崔13]設置保存點

 [崔14]給李四減支100萬

 [崔15]給張三加上100萬

 [崔16]查看李四余額

 [崔17]發現李四余額小於0,回滾到指定還原點!即撤銷了李四給張三轉賬100萬的操作

 [崔18]注意,一定要提交事務,因為回滾到指定保存點不會結束事務!保存點之前的操作沒有被回滾,只能提交了才能真正把沒有回滾的操作執行了。

 [c19]不能允許出來的事情!

 [c20]三種讀問題都能處理

 [c21]臟讀、不可重復讀,不能處理幻讀

 [c22]只能處理臟讀,不能處理不可重復讀和幻讀。

 [c23]啥也不處理!

 [崔24]實現DataSource接口

 [崔25]用來存放連接的list

 [崔26]加載配置文件中的配置信息到props屬性中

 [崔27]構造器中向list中存放5個連接對象

 [崔28]創建連接對象

 [崔29]對連接對象進行裝飾,把池自身的引用傳遞給連接裝飾對象

 [崔30]把裝飾后的連接對象添加到list中

 [崔31]裝飾后的連接對象的close()方法會調用本方法“歸還”連接。

 [崔32]程序會調用本方法來獲取連接對象!本方法會在list中移除一個連接返回給程序使用。

 [崔33]省略了DataSource中其他沒有的方法。

 [崔34]ConnectionWrapper是Connection的裝飾模板類!這個類要自己提供!

 [崔35]本類需要擁有池對象的引用,用來歸還連接。

 [崔36]當程序調用了close()方法時,把當前連接對象添加到池中。

 [崔37]基本配置

 [崔38]最大連接數

 [崔39]最大空閑連接數

 [崔40]初始化連接數

 [崔41]最小空閑連接數

 [崔42]最大等待毫秒數

 [崔43]關閉連接只是把連接歸還給池!

 [崔44]基本配置

 [崔45]每次的增量為5

 [崔46]初始化連接數

 [崔47]最少連接數

 [崔48]最多連接數

 [崔49]默認配置

 [崔50]命名配置

 [崔51]不用定配置文件名稱,因為配置文件名必須是c3p0-config.xml,這里使用的是默認配置。

 [崔52]使用名為oracle-config的配置。

 [崔53]把一行記錄轉換成一個Map,其中鍵為列名稱,值為列值

 [崔54]把轉換集轉換成List<Map>,其中每個Map對應一行記錄

 [崔55]把結果集轉換成一個Bean對象,在使用BeanHandler時需要指定Class,即Bean的類型

 [崔56]把結果集轉換成List<Bean>,其中每個Bean對應一行記錄

 [崔57]多行單例處理器,即獲取name列數據

 [崔58]單行單列處理器,一般用於聚合查詢,在使用ScalarHandler時可以指定列名,如果不指定,默認為第1列。

 [崔59]對聚合函數的查詢結果,有的驅動返回的是Long,有的返回的是BigInteger,所以這里我們把它轉換成Number,Number是Long和BigInteger的父類!然后我們再調用Number的intValue()或longValue()方法就OK了。

 [崔60]注意,這里是二維數組,這個二維數組有10個一維數組。

 [崔61]執行批處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM