JavaWeb學習筆記之Jdbc(二)

1.事務

1.1事務的四大特性(ACID)

  • 1.原子性:事務中的全部操做要麼所有執行成功,要麼執行所有失敗。
  • 2.一致性:事務執行後,數據庫狀態與其它業務規則保持一致。
  • 3.隔離性:隔離性是指在併發操做中,不一樣事務之間應該隔離開來,使每一個併發中的事務不會相互干擾。
  • 4.持久性:一旦事務提交成功,事務中全部的數據操做都必須被持久化到數據庫中。即便提交事務後數據庫立刻崩潰,在數據庫重啓後,也必須能保證經過某種機制恢復數據。

1.2mysql中操做事務

在控制檯中輸入語句:start transaction;即開始事務。java

在控制檯中輸入語句:commit transaction;即提交事務。mysql

在控制檯中輸入語句:rollback;回滾事務,即在此事務中執行的操做所有無效,數據庫回到start transaction;以前(前提是使用該語法前沒有執行commit transaction;操做)。sql

1.3Jdbc中操做事務

在Jdbc中處理事務都是經過Connection對象完成的,同一事務中的全部操做,都在使用同一個Connection對象。數據庫

setAutoCommit(boolean);設置是否自動提交事務,若是爲true表示自動提交(默認值就是true),也就是每條執行的sql語句都是一個單獨的事務,若是設置false,那麼就至關於開啓了事務了。con.setAutoCommit(false);語句表示開啓事務。服務器

con.commit();提交併結束事務。多線程

con.rollback();回滾事務。併發

2.事務的隔離級別

2.1事務的併發讀問題

  • 髒讀:讀取到另外一份事務未提交數據,即讀到了髒數據。
  • 不可重複讀:兩次讀取不一致。對統一記錄的兩次讀取不一致,由於另外一事務對該記錄作了修改。
  • 幻讀:又叫虛讀。對同一張表的兩次查詢不一致,由於另外一事務進行了插入了一條記錄的操做。

2.2四大隔離級別(防止上述問題)

  • a.SERIALIZABLE(串行化):不會出現任何併發問題,由於它是對同一數據的訪問是串行的,非併發訪問的。性能最差,可能致使死鎖。
  • b.REPEATABLE READ(可重複讀)(mysql默認級別):防止髒讀和不可重複讀,不能處理幻讀問題。性能比a的好。
  • c.READ COMMITTED(讀已提交數據)(Oracle默認級別):防止髒讀,沒有處理不可重複讀,也沒有處理幻讀。性能比上述b好。
  • d.READ UNCOMMITTED(讀未提交數據):可能出現任何事務併發問題。性能最好。但基本沒人用。

2.3查看mysql的隔離級別

在控制檯中輸入語句:select @@tx_isolation;ide

也能夠經過下面命令來設置隔離級別:set transaction isolationlevel[4選1];工具

2.4在Jdbc中設置隔離級別

con.setTransactionisolation[int lever];性能

3.數據庫鏈接池

用戶每次請求都須要向數據庫得到連接,而數據庫建立鏈接一般須要消耗相對較大的資源,建立時間也較長。假設網站一天10萬訪問量,數據庫服務器就須要建立10萬次鏈接,極大的浪費數據庫的資源,而且極易形成數據庫服務器內存溢出、拓機。以下圖所示:

數據庫鏈接是一種關鍵的有限的昂貴的資源,這一點在多用戶的網頁應用程序中體現的尤其突出.。對數據庫鏈接的管理能顯著影響到整個應用程序的伸縮性和健壯性,影響到程序的性能指標。數據庫鏈接池正式針對這個問題提出來的。數據庫鏈接池負責分配,管理和釋放數據庫鏈接,它容許應用程序重複使用一個現有的數據庫鏈接,而不是從新創建一個。以下圖所示:

數據庫鏈接池在初始化時將建立必定數量的數據庫鏈接放到鏈接池中, 這些數據庫鏈接的數量是由最小數據庫鏈接數來設定的.不管這些數據庫鏈接是否被使用,鏈接池都將一直保證至少擁有這麼多的鏈接數量.鏈接池的最大數據庫鏈接數量限定了這個鏈接池能佔有的最大鏈接數,當應用程序向鏈接池請求的鏈接數超過最大鏈接數量時,這些請求將被加入到等待隊列中。

數據庫鏈接池的最小鏈接數和最大鏈接數的設置要考慮到如下幾個因素:

  • 1.最小鏈接數(MinActive):是鏈接池一直保持的數據庫鏈接,因此若是應用程序對數據庫鏈接的使用量不大,將會有大量的數據庫鏈接資源被浪費。

  • 2.最大鏈接數(MaxActive):是鏈接池能申請的最大鏈接數,若是數據庫鏈接請求超過次數,後面的數據庫鏈接請求將被加入到等待隊列中,這會影響之後的數據庫操做。

  • 3.若是最小鏈接數與最大鏈接數相差很大:那麼最早鏈接請求將會獲利,以後超過最小鏈接數量的鏈接請求等價於創建一個新的數據庫鏈接.不過,這些大於最小鏈接數的數據庫鏈接在使用完不會立刻被釋放,他將被放到鏈接池中等待重複使用或是空間超時後被釋放。

3.1池參數(全部池參數都有默認值)

設置初始化大小:connection.setInitialSize();
設置最小空閒鏈接數:connection.setMinIdle();
設置最大空閒鏈接數:connection.setMaxIdle();
設置最小鏈接數:connection.setMinActive();
設置最大鏈接數:connection.setMaxActive();
設置增量:一次建立的最小單位。
設置最大的等待時間:connection.setMaxWait();

3.2四大鏈接參數

鏈接池也是使用Jdbc中的四大鏈接參數和驅動jar包來完成建立鏈接對象。

3.3實現的接口

鏈接池必須實現javax.sql.DataSource接口。

從鏈接池返回的Connection對象,它的close()方法不同凡響。調用它的close()方法不是關閉,而是把鏈接歸還給池。

3.4DBCP數據庫鏈接池

DBCP是Apache軟件基金組織下的開源鏈接池實現,要使用DBCP數據源,須要應用程序應在系統中增長以下兩個jar文件:

  • Commons-dbcp.jar:鏈接池的實現
  • Commons-pool.jar:鏈接池實現的依賴庫

Demo:

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
public class Demo{
	public static void main(String[] args)
	{
		/*
		 *1.建立鏈接池對象
		 *2.配置四大參數
		 *3.配置池參數
		 *4.獲得鏈接對象
		 */
		BasicDataSource dataSource=new BasicDataSource();
		
		dataSource.setDriverClassName(「com.mysql.jdbc.Driver」);
		dataSource.setUrl(「jdbc:mysql://localhost:3306/mydb」);
		dataSource.setUsername(「root」);
		dataSource.setPassword(123);
		
		dataSource.setMaxActive(20);
		dataSource.setMinIdle(3);
		dataSource.setMaxWait(1000);
		
		Connection con=dataSource.getConnection();
		System.out.println(con);
		
	  /*
		*鏈接池內部使用四大參數建立了鏈接對象,即mysql驅動提供的Connection
		*鏈接池使用mysql的鏈接對象進行了裝飾,只對close()方法進行了加強!
		*裝飾以後的Connection的close()方法,用來把當前鏈接歸還給池
		*/
		con.close();//把鏈接歸還給池。
	}
}

 

既然談到裝飾,那下面咱們就在下文3.7中來談談裝飾者模式。

3.5c3p0數據庫鏈接池

c3p0,全名叫ComboPooledDataSource;

須要導入的jar包:

  • 鏈接池的實現:c3p0-0.9.5.2.jar
  • 依賴庫:mchange-commons.jar

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Demo{
	public static void main(String[] args)
	{
		//建立鏈接池對象
		ComboPooledDataSource dataSource=new ComboPooledDataSource();
		
		//進行四大參數的配置
		dataSource.setDriverClass(「com.mysql.jdbc.Driver」);
		dataSource.setJdbcUrl(「jdbc:mysql://localhost:3306/mydb」);
		dataSource.setUser("root");
		dataSource.setPassword("123");
		
		//池配置
		dataSource.setAcquireIncrement(5);
		dataSource.setInitialPoolSize(20);
		dataSource.setMinPoolSize(2);
		dataSource.setMaxPoolSize(50);
		
		Connection con=dataSource.getConnection();
		System.out.println(con);
		
		con.close();
	}
}

 

3.5.1c3p0配置文件的使用

配置文件要求:

文件名稱:必須叫c3p0-config.xml。
文件的位置:必須在src下。

c3p0配置文件:

寫入配置文件後的Demo:

1
2
3
4
5
6
7
8
9
public class Demo{
	public static void main(String[] args)
	{
		//在建立鏈接池對象時,這個對象就會自動加載配置文件,不用咱們來指定。
		ComboPooledDataSource data=new comboPooledDataSource();
		Connection con=data.getConnection();
		System.out.println(con);
	}
}

 

3.6Tomcat配置數據庫鏈接池

3.6.1Tomcat配置JNDI資源

JNDI:java命名和目錄接口。做用:在服務器上配置資源,而後經過統一的方式來獲取配置的資源。

首先須要在Tomcat/conf/Catelina/localhost目錄下新建文件名: 項目名.xml

在該.xml文件中寫入如下內容

3.6.1獲取資源的代碼

1
2
3
4
5
Context initCtx=new InitialContext();//建立一個上下文。

Context envCtx=(Context) initCtx.lookup(「java:comp/env」);//這個路徑是固定的不能改。

MyBean bean=(MyBean)envCtx.lookup(「bean/MyBeanFactory」);//經過該上下文進行二次查找才能找到資源。

Demo:測試類

3.7裝飾者模式

將對象加強的手段有:

  • 繼承

    缺點:1.加強的內容是死的,不能動。2.被加強的對象也是死的。

  • 裝飾者模式

    特色:1.加強的內容是不能修改的。2.被加強的對象能夠是任意的。

  • 動態代理(AOP):之後再詳講,博客出來後會給出連接。

下面經過一個簡單的例子來對裝飾者模式進行講解

class 咖啡類 {};
class 加奶咖啡 extends 咖啡類 {};
class 加糖咖啡 extends 咖啡類 {};
class 加鹽咖啡 extends 咖啡類 {};

咖啡 a=new 加糖咖啡();
咖啡 b=new 加鹽咖啡(a);//對a進行裝飾,就是給a加鹽
咖啡 c=new 加奶咖啡(b);//對b進行裝飾,就是給b加奶

裝飾者模式在Java API中的IO流中用到的不少。如BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter、ObjectInputStream、ObjectOutputStream這幾個都是運用了裝飾模式的裝飾流。關於的IO流的詳情見下篇博客Java之IO流詳解

4.ThreadLocal

早在JDK 1.2的版本中就提供Java.lang.ThreadLocal,ThreadLocal爲解決多線程程序的併發問題提供了一種新的思路。使用這個工具類能夠很簡潔地編寫出優美的多線程程序。

4.1Thread API

  • void set(Object value);設置當前線程的線程局部變量的值。
  • Object get();該方法返回當前線程所對應的線程局部變量。
  • void remove();將當前線程局部變量的值刪除,目的是爲了減小內存的佔用。須要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,因此顯式調用該方法清除線程的局部變量並非必須的操做,但它能夠加快內存回收的速度。

4.2ThreadLocal內部結構

ThreadLocal內部用Map來保存數據。雖然在使用上述API時沒有給出鍵,但其實它內部使用了當前線程做爲鍵。內部結構見下面demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ThreadLocal
{
	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());
	}
}

 

5.dbtils結果集處理器介紹

須要導入的jar包:

  • common-dbutil.jar
  • c3p0.jar
  • mchange-commons.jar

關鍵要獲得QueryRunner對象,而後調用其各類方法。

  • update()方法:

    1.int update(String sql,Object… params) 可執行增刪改語句。
    2.重載方法int update(Connection con,String sql, Object… params)須要調用者提供Connection,這說明本方法再也不管理Connection了。本重載方法支持事務。

  • query()方法:

    1.T query (String sql,ResultSetHandler rsh,Object… params)可執行查詢操做。
    2.重載方法:T query(Connection con,String sql,ResultSetHandler rsh,Object… params); 本重載方法支持事務。它會先獲得ResultSet,而後調用rsh的handle()把rs轉換成須要的類型。

  • ResultSetHandler接口

    1.BeanHandler(單行)-->構造器須要一個class類型的參數,用來把一行結果轉換成指定類型的javaBean對象。
    2.BeanListHandler(多行)—>構造器也是須要一個Class類型的參數,用來把一行結果集轉換成一個javabean,哪麼多行就是轉換成List對象,一堆javabean。
    3.MapHandler(單行)—>把一行結果集轉換成Map對象。
    4.MapListHandler(多行)—>把一行記錄轉換成一個Map,多行就是多個Map,即List。
    5.ScalarHandler(單行單列)->同來用於select count(*)from t_stu語句,結果集是單行單列的,它返回一個Object,就是count(*)的值,爲long類型。

dbutil結果處理集原理代碼:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
package demo;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Created by codingBoy on 16/10/19.
 */
 public class QR<T>
 {
   	private DataSource dataSource;

   public QR(DataSource dataSource)
   {
       this.dataSource=dataSource;
   }

   public QR(){
       super();
   }

   public int update(String sql,Object... params)
   {
       Connection con=null;
       PreparedStatement pstmt=null;

       try
       {
           con=dataSource.getConnection();//經過鏈接池獲得鏈接對象
           pstmt=con.prepareStatement(sql);

           initParams(pstmt,params);//給出參數

           return pstmt.executeUpdate();//調用update執行增、刪、該
       }catch (Exception e)
       {
           throw new RuntimeException(e);
       }finally {
           try{
               if (pstmt!=null) pstmt.close();
               if (con!=null) con.close();
           }catch (SQLException e){}
       }
   }

   //給參數賦值
   public void initParams(PreparedStatement pstmt,Object... params) throws SQLException {
       for (int i = 0; i < params.length; i++)
       {
           pstmt.setObject(i+1,params[i]);
       }
   }

   public T query(String sql,RsHandler<T> rh,Object... params) throws SQLException {
       Connection con=null;
       PreparedStatement pstmt=null;
       ResultSet rs=null;

       try
       {
           con=dataSource.getConnection();//經過鏈接池獲得鏈接對象
           pstmt=con.prepareStatement(sql);

           initParams(pstmt,params);//給出參數

           rs=pstmt.executeQuery();//調用update執行增、刪、該

           return rh.handle(rs);
       }catch (Exception e)
       {
           throw new RuntimeException(e);
       }finally {
           
           if (rs!=null) rs.close();
           if (pstmt!=null) pstmt.close();
           if (con!=null) con.close();

       }
   }

   interface RsHandler<T>
   {
       public T handle(ResultSet rs);
   }
   }

 

這樣咱們之後對數據庫進行增、刪、改操做時,只需寫如下代碼便可:

1
2
3
4
5
1.QueryRunner qr=new QueryRunner(JdbcUtils.getDataSource);//建立QueryRunner對象,並傳入鏈接池對象

2.String sql="insert into user values(?,?,?,?);//給出sql語句模板
3.Object[] params={參數1,參數2,參數3,參數4};//傳入參數
4.qr.update(sql,params);//調用qr方法。

經過這簡單的四步就能夠對數據庫進行增刪改了。

對數據庫進行查詢操做時,只需寫如下代碼:

1
2
3
4
5
6
7
8
9
10
1.QueryRunner qr=new QueryRunner(JdbcUtils.getDataSource);//建立QueryRunner對象,並傳入鏈接池對象
2.String sql="select * from user where id=?";//給出sql語句模板
3. Object[] params={參數};//傳入參數
//4. ResultSetHandler<Object> rsh=new ResultSetHandler(){
//		@Override
//		public Object handle(Result rs) throws SQLException{
//			return null;
//		}
//	};
5.Object object=qr.query(sql,new BeanHandler<Object>(Object.class),params);

 

經過這幾步便可實現對數據的查詢操做了。

下面的解釋寫給本身看的:關於connection是否關閉的問題

在jar包中,QueryRunner類的update(沒有connection參數的)方法,在finally中將connection進行了關閉;在update(有connection參數的)方法中,在finally中沒有對connection進行關閉(暫時這麼記吧,否則要是進行關閉了的話,在傳智播客寫的小工具封裝類TxQueryRunner中將connection傳入JdbcUtils的releaseConnecion()方法中對connection進行關閉時會出現報錯)。

在講到事務時,咱們會對QueryRunner進行再次封裝。上述寫出的QueryRunner的代碼只是包中的QueryRunner源碼方法的一部分(由於源碼中還有不少的重載方法),咱們會經過另外一個類TxQueryRunner(較QueryRunner多出的一個功能就是它支持事務)繼承該類,在TxQueryrunner類中,對connection進行了判斷:若connection爲事務中的connection則在TxqueryRunner的update()方法中不對connection進行關閉,而是在commitTransaction()即提交事務時進行關閉;若connection爲普通鏈接,則將connection進行關閉。那麼之後咱們在DAO中要獲取的就不是QueryRunner對象,而是經過QueryRunner qr=new TxQueryRunner();獲取TxQueryRunner對象了。

相關文章
相關標籤/搜索