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 結束事務:commit或rollback。
在執行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(); } 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(); //操作2(李四加上1W) pstmt.setDouble(1, 10000); pstmt.setString(2, "ls"); pstmt.executeUpdate(); // 設置保存點 Savepoint sp = con.setSavepoint();[崔13]
//操作3(李四減去100W) pstmt.setDouble(1, -1000000); pstmt.setString(2, "ls"); pstmt.executeUpdate(); //操作4(張三加上100W) pstmt.setDouble(1, 1000000); pstmt.setString(2, "zs"); pstmt.executeUpdate(); //操作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"); 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"); 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語句。
- 創建QueryRunner
構造器:QueryRunner();
- 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
- 創建QueryRunner
構造器:QueryRunner(DataSource)
- 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]執行批處理