傳智播客JAVA培訓第12天

day12總結

今日內容

事務     連接池

一、事務

事務概述

爲了方便演示事務,我們需要創建一個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 一致性(Consistency):事務執行後,數據庫狀態與其它業務規則保持一致。如轉賬業務,無論事務執行成功與否,參與轉賬的兩個賬號餘額之和應該是不變的。

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

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

MySQL中的事務

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

l 開啓事務:start transaction

l 結束事務:commitrollback

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

下面演示zsli轉賬10000元的示例:


START TRANSACTION;

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

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

ROLLBACK;

START TRANSACTION;

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

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

COMMIT;

START TRANSACTION;

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

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

quit;

JDBC事務

  1、JDBC中的事務

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

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

l commit():提交結束事務;

l rollback():回滾結束事務。

public void transfer(boolean b) {

Connection con = null;

PreparedStatement pstmt = null;

try {

con = JdbcUtils.getConnection();

//手動提交

con.setAutoCommit(false);

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();

catch(Exception e) {

//回滾事務

if(con != null) {

try {

con.rollback();

catch(SQLException ex) {}

}

throw new RuntimeException(e);

finally {

//關閉

JdbcUtils.close(con, pstmt);

}

}

2、保存點(瞭解)

保存點是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);

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();

//操作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);

      //如果李四餘額爲負數,那麼回滾到指定保存點

if(balance < 0) {

con.rollback(sp);

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

}

//提交事務

con.commit();

catch(Exception e) {

//回滾事務

if(con != null) {

try {

con.rollback();

catch(SQLException ex) {}

}

throw new RuntimeException(e);

finally {

//關閉

JdbcUtils.close(con, pstmt);

}

}

事務隔離級別

1 、事務的併發讀問題

l 髒讀:讀取到另一個事務未提交數據;

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

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

2、五大併發事務問題

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

l 髒讀(dirty read):讀到未提交更新數據

時間

轉賬事務A

取款事務B

T1

開始事務

T2

開始事務

T3

查詢賬戶餘額爲1000

T4

取出500元把餘額改爲500

T5

查看賬戶餘額爲500元(髒讀)

T6

撤銷事務,餘額恢復爲1000

T7

匯入100元把餘額改爲600

T8

提交事務

  

A事務查詢到了B事務未提交的更新數據,A事務依據這個查詢結果繼續執行相關操作。但是接着B事務撤銷了所做的更新,這會導致A事務操作的是髒數據。(這是絕對不允許出現的事情)

l 虛讀(phantom read):讀到已提交插入數據

時間

統計金額事務A

轉賬事務B

T1

開始事務

T2

開始事務

T3

統計總存款數爲10000

T4

新增一個存款賬戶,存款爲100

T5

提交事務

T6

再次統計總存款數爲10100

  A事務第一次查詢時,沒有問題,第二次查詢時查到了B事務已提交的新插入數據,這導致兩次查詢結果不同。(在實際開發中,很少會對相同數據進行兩次查詢,所以可以考慮是否允許虛讀)

l 不可重複讀(unrepeatable read):讀到已提交更新數據

時間

取款事務A

轉賬事務B

T1

開始事務

T2

開始事務

T3

查詢賬戶餘額爲1000

T4

查詢賬戶餘額爲1000

T5

取出100元,把餘額改爲900

T6

提交事務

T7

查詢賬戶餘額爲900元(與T4讀取的一不一致)

  不可重複讀與虛讀有些相似,都是兩次查詢的結果不同。後者是查詢到了另一個事務已提交的新插入數據,而前者是查詢到了另一個事務已提交的更新數據。

3、四大隔離級別 

隔離級別

髒讀

不可重複讀

虛讀

第一類丟失更新

第二類丟失更新

READ UNCOMMITTED

允許

允許

允許

不允許

允許

READ COMMITTED

不允許

允許

允許

不允許

允許

REPEATABLE READ

不允許

不允許

允許

不允許

不允許

SERIALIZABLE

不允許

不允許

不允許

不允許

不允許

4、哪種隔離級別最好

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

SERIALIZABLE(串行化)

當數據庫系統使用SERIALIZABLE隔離級別時,一個事務在執行過程中完全看不到其他事務對數據庫所做的更新。當兩個事務同時操作數據庫中相同數據時,如果第一個事務已經在訪問該數據,第二個事務只能停下來等待,必須等到第一個事務結束後才能恢復運行。因此這兩個事務實際上是串行化方式運行。

REPEATABLE READ(可重複讀)

當數據庫系統使用REPEATABLE READ隔離級別時,一個事務在執行過程中可以看到其他事務已經提交的新插入的記錄,但是不能看到其他事務對已有記錄的更新。

READ COMMITTED(讀已提交數據)

  當數據庫系統使用READ COMMITTED隔離級別時,一個事務在執行過程中可以看到其他事務已經提交的新插入的記錄,而且還能看到其他事務已經提交的對已有記錄的更新。

READ UNCOMMITTED(讀未提交數據)

  當數據庫系統使用READ UNCOMMITTED隔離級別時,一個事務在執行過程中可以看到其他事務沒有提交的新插入的記錄,而且還能看到其他事務沒有提交的對已有記錄的更新。

你可能會說,選擇SERIALIZABLE,因爲它最安全!沒錯,它是最安全,但它也是最慢的!而且也最容易產生死鎖。四種隔離級別的安全性與性能成反比!最安全的性能最差,最不安全的性能最好!

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

5、MySQL隔離級別

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

select @@session.tx_isolation

select @@global.tx_isolation

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

set global transaction isolationlevel [41]

set session transaction isolationlevel [41]

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。

事務總結:

事務的特性:ACID

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

事務的隔離級別: READ_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE。多個事務併發執行時才需要考慮併發事務。


二、數據庫連接池
1、數據庫連接池的概念

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

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

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

3、自定義連接池(ItcastPool

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

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

ItcastPool.java

public class ItcastPool implements DataSource {

private static Properties props = new Properties();

private List<Connection> list = new ArrayList<Connection>();

static {

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() throws SQLException {

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

Connection con = DriverManager.getConnection(

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

props.getProperty("password"));

ItcastConnection conWapper = new ItcastConnection(con, this);

list.add(conWapper);

}

}

public void add(Connection con) {

list.add(con);

}

public Connection getConnection() throws SQLException {

if(list.size() > 0) {

return list.remove(0);

}

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

}

    ......

}

ItcastConnection.java

public class ItcastConnection extends ConnectionWrapper {

private ItcastPool pool;

public ItcastConnection(Connection con, ItcastPool pool) {

super(con);

this.pool = pool;

}

@Override

public void close() throws SQLException {

pool.add(this);

}

}

DBCP

1、什麼是DBCP

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

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

ds.setMaxIdle(10);

ds.setInitialSize(10);

ds.setMinIdle(2);

ds.setMaxWait(1000);

Connection con = ds.getConnection();

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

con.close();

}

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=trueMySQL開啓預編譯功能

#cachePrepStmts=trueMySQL開啓緩存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);

ds.setInitialPoolSize(20);

ds.setMinPoolSize(2);

ds.setMaxPoolSize(50);

Connection con = ds.getConnection();

System.out.println(con);

con.close();

}

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

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

<c3p0-config>

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

</default-config>

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

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

public void fun2() throws PropertyVetoException, SQLException {

ComboPooledDataSource ds = new ComboPooledDataSource();

Connection con = ds.getConnection();

System.out.println(con);

con.close();

}

public void fun2() throws PropertyVetoException, SQLException {

ComboPooledDataSource ds = new ComboPooledDataSource("orcale-config");

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這個屬性,而是應該去配置urlusername等屬性。

<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 Contextjavax.naming.Context

l InitialContextjavax.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 

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只有一個方法(doGetdoPost),如果項目大一些,那麼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參數,這個參數的值就是要請求的方法名稱,這樣BaseServletservice()才能通過方法名稱來調用目標方法。例如某個鏈接如下:

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

// 反射方法目標方法,也就是說,如果methodNameadd,那麼就調用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簡介

DBUtilsApache Commons組件中的一員,開源免費!

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

DBUtilsJar包:dbutils.jar

2、DBUtils主要類

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

l QueryRunner

Ø update():執行insertupdatedelete

Ø query():執行select語句;

Ø batch():執行批處理。

3、QueryRunner之更新

QueryRunnerupdate()方法可以用來執行insertupdatedelete語句。

1. 創建QueryRunner

構造器:QueryRunner();

2. 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)

2. 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,然後通過rhResultSet轉換成對應的類型再返回。

@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(), "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());

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), "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));

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"));

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());

int cnt = number.intValue();

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][];//表示 要插入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(sql, params);

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章