1. 程式人生 > >事務、資料庫連線池

事務、資料庫連線池

事務、資料庫連線池

事務

指的是一組操作,裡面包含許多個單一的邏輯,如果都成功了,就執行提交(commit)只要有一個邏輯沒有執行成功,那麼都算失敗,所有的資料都回歸到最初的狀態(回滾rollback)

為什麼要有事務

為了確保邏輯的成功


使用程式碼方式演示事務

程式碼裡面的事務,主要是針對連線來的,通過conn,setAutoCommit(false)來關閉自動提交的設定

public void testTransaction(){
	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;
	try{
		conn = JDBCUtil.getConn();
		
		//連線,事務預設就是自動提交的,關閉自動提交
		conn.setAutoCommit
(false); String sql = "update account set money = money - ? where id = ?" ps = conn.prepareStatement(sql); //扣錢 ps.setInt(1,100); ps.setInt(2,1); ps.executeUpdate(); //此處為演示資料庫錯誤 int a = 10/0 //加錢,給ID為2 加100塊錢 ps.setInt(1,-100); ps.setInt(2,2); ps.executeUpdate(); }catch(SQLException e)
{ try{ conn.rollback(); }catch(SQLException e1){ e1.printStackTrace(); } e1.printStackTrace(); }finally{ JDBCUtil.release(conn,ps,rs); } }

事務的特性ACID

1.原子性

指的是事務中包含的邏輯不可分割

2.一致性

指的是事務執行前後,資料完整性

3.隔離性

指的是事務在執行期間不應該受到其他事務的影響

4.永續性

指的是事務執行成功,那麼資料應該持久儲存到磁碟上

事務的安全隱患

讀:
1.髒讀

一個事務讀到另外一個事務還未提交的資料
A,B兩個視窗,當A視窗的隔離級別為Read Uncommitted(讀未提交),B視窗正在執行更新,但是還未提交,此時A視窗已經進行查詢

解決辦法:

設定A視窗的隔離級別為讀已提交(這個隔離級別能夠遮蔽髒讀的現象,但是引發另一個問題,不可重複讀
A,B兩個視窗都開啟事務,在B視窗執行更新操作
在A視窗執行的兩次查詢結果不一致,一次是在B視窗提交事務之前,一次是在B視窗提交事務之後


2.不可重複讀

一個事務讀到了另外一個事務提交的資料,造成了前後兩次查詢結果不一致


3.幻讀

一個事務讀到了另一個事務已提交的插入的資料,導致多次查詢結果不一致


如果想把以上問題都解決,那麼要用到最高的隔離級別:Serializable(可序列化)

set session transaction isolayion level serializable;

如果有一個連線的隔離級別設定了序列化,那麼誰先打開了事務,誰就有了先執行的權利,後來開啟事務的只能等前面的事務提交或者回滾才能執行。但是這種隔離級別一般比較少用,容易造成效能上的問題,效率比較低

按效率分,隔離級別從高到低:
讀未提交>讀可提交>可重複讀>可序列化

按攔截程度分,隔離級別從高到低
可序列化>可重複讀>讀已提交>讀未提交

寫:
丟失更新

A和B同時開啟事務,A先提交更新,B後提交更新,B的更新會沖掉A的更新

解決方法:
1.悲觀鎖(排他鎖)

select * from account for update
A和B同時開啟事務,如果A先查詢,此時B介面會卡住,無法查詢,要等到A提交或者回滾,B才可以進行更新資料

2.樂觀鎖
想要實現需要程式設計師自己寫程式碼

select * from account
A和B同時開啟事務,A事務先提交,資料庫version變為1,此時B事務提交的時候,要比對資料庫的version和自己的version,如果不一樣,不允許提交


事務總結

1.常用程式碼

conn.setAutoCommit(false);

conn.commit();
conn.rollback();

2.事務只是針對連線物件,如果再開一個連線物件,那麼預設提交
3.事務是會自動提交的
4.安全隱患有讀和寫兩方面,讀會產生髒讀、不可重複讀、幻讀等隱患,寫會產生丟失更新的隱患
5.隔離級別有四種,讀未提交引發髒讀),讀已提交(解決髒讀,引發不可重複讀),可重複讀(解決髒讀、不可重複讀,未解決的是幻讀),可序列化(解決:髒讀、不可重複讀、幻讀)
6.mySql預設的隔離級別是可重複讀,Oracle預設的隔離級別是讀已提交

資料庫連線池

一開始先在記憶體中開闢一塊空間(集合),先往池子裡面放置多個連線物件,後面需要連線的話,直接拿池子中的練級物件,不要自己建立連線物件,使用完畢要記得歸還連線,確保物件能迴圈利用

資料庫連線池的簡單搭建

/*
 *1.開始建立10個連線
 *2.來的程式通過getConnection獲取連線
 *3.用完歸還
 *4.擴容
 */
puublic class MyDataSource implements DataSource{
	public MyDataSource(){
		for(int i=0;i<10;i++){
			//JDBCUtil是自己寫的jdbc工具類
			Connection conn = JDBCUtil.getConnection();
			list.add(conn);
		}
	}
	public Connection getConnection()throws SQLException{
		//檢查池子裡面還有沒有連線
		if(list.size()==0){
			for(int i=0;i<5;i++){
			//JDBCUtil是自己寫的jdbc工具類
			Connection conn = JDBCUtil.getConnection();
			list.add(conn);
		}
		Connection conn = list.remove(0);
		return conn;
	}

	//歸還
	public void addBack(Connection conn){
		list.add(conn);
	}
}

資料庫連線池的簡單使用

public void testPool(){
	Connection conn = null;
	PreparedStatement ps = null;
	MyDataSource dataSource = new MyDataSource();
	try{
		conn = dataSource.getConnection();

		String sql = "insert into account values(null,"xxx",10)";
		ps = con.prepareStatement(sql);
		ps.executeUpdate();
	}catch(SQLException e){
		e.printStackTrace();
	}finally{
		try{
			ps.close();
		}catch(SQLException e){
			e.printStackTrace();
		}
	}

}

自定義資料庫連線池的缺點

以上程式碼其實是我們自己寫的一個數據庫連線池,它實現了一個addBack()方法,但是這個方法DataSource介面中並沒有定義,如果要用這個方法還得另外去記。
所以,這種自定義資料庫的寫法的缺點是無法面向介面程式設計。

面向介面程式設計:
在這裡插入圖片描述

解決方法:
裝飾者模式----面向介面程式設計
寫一個ConnectionWrap.class

public class ConnectionWrap implements Connection{
	Connection connection = null;
	List<Connection> list;
	public ConnectionWrap(Connection connection,List<Connection> list){
		super();
		this.connection = connection;
		this.list = list
	}

	//重寫close()方法
	public void close() throws SQLException{
		connection.close();
	}
}

修改自定義的MyDataSource.class

puublic class MyDataSource implements DataSource{
	public MyDataSource(){
		for(int i=0;i<10;i++){
			//JDBCUtil是自己寫的jdbc工具類
			Connection conn = JDBCUtil.getConnection();
			list.add(conn);
		}
	}
	public Connection getConnection()throws SQLException{
		//檢查池子裡面還有沒有連線
		if(list.size()==0){
			for(int i=0;i<5;i++){
			//JDBCUtil是自己寫的jdbc工具類
			Connection conn = JDBCUtil.getConnection();
			list.add(conn);
		}
		Connection conn = list.remove(0);
		
		//把這個物件丟擲去的時候,對這個物件進行包裝
		Connection connection = New ConnectionWrap(conn,list);
		return connection;
	}
}