事務、資料庫連線池
事務、資料庫連線池
事務
指的是一組操作,裡面包含許多個單一的邏輯,如果都成功了,就執行提交(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;
}
}