/*
########事務&數據庫連接池&DBUtils
######事務
> Transaction 其實指的一組操作,里面包含許多個單一的邏輯。
只要有一個邏輯沒有執行成功,那么都算失敗。 所有的數據都回歸到最初的狀態(回滾)
####為什么要有事務?
> 為了確保邏輯的成功。 例子: 銀行的轉賬。
?
?
###使用命令行方式演示事務。
* 開啟事務
start transaction;
* 提交或者回滾事務
commit; 提交事務, 數據將會寫到磁盤上的數據庫
rollback ; 數據回滾,回到最初的狀態。
1. 關閉自動提交功能。
2. 演示事務
?
#####使用代碼方式演示事務
> 代碼里面的事務,主要是針對連接來的。
>
>
> 1. 通過conn.setAutoCommit(false )來關閉自動提交的設置。
> 2. 提交事務 conn.commit();
> 3. 回滾事務 conn.rollback();
//注意當數據庫不支持回滾的時候有如下原因
* 1.數據庫引擎不是innodb 而是myisam,不支持事務回滾。
* 2.在rollback之前 已經通過connection.commit 提交改動 無法回滾
* 3.mysql默認create drop alter 等涉及到表修改,會隱式結束當前會話中的任何活動事務,直接提交,無法回滾。
@Test
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);
//扣錢, 扣ID為1 的100塊錢
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();
//成功: 提交事務。
conn.commit();
} catch (SQLException e) {
try {
//事變: 回滾事務
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
}finally {
JDBCUtil.release(conn, ps, rs);
}
}
###事務的特性
* 原子性
> 指的是 事務中包含的邏輯,不可分割。
* 一致性
> 指的是 事務執行前后。數據完整性
* 隔離性
> 指的是 事務在執行期間不應該受到其他事務的影響
* 持久性
> 指的是 事務執行成功,那么數據應該持久保存到磁盤上。
###事務的安全隱患
> 不考慮隔離級別設置,那么會出現以下問題。
* 讀
> 臟讀 不可重讀讀 幻讀.
* 臟讀
> 一個事務讀到另外一個事務還未提交的數據
* 不可重復讀
> 一個事務讀到了另外一個事務提交的數據 ,造成了前后兩次查詢結果不一致。
####讀未提交 演示
1. 設置A窗口的隔離級別為 讀未提交
2. 兩個窗口都分別開啟事務
* 寫
> 丟失更新
?
#### 讀已提交演示
1. 設置A窗口的隔離級別為 讀已提交
2. A B 兩個窗口都開啟事務, 在B窗口執行更新操作。
3. 在A窗口執行的查詢結果不一致。 一次是在B窗口提交事務之前,一次是在B窗口提交事務之后。
> 這個隔離級別能夠屏蔽 臟讀的現象, 但是引發了另一個問題 ,不可重復讀。
###可串行化
> 如果有一個連接的隔離級別設置為了串行化 ,那么誰先打開了事務, 誰就有了先執行的權利, 誰后打開事務,誰就只能得著,等前面的那個事務,提交或者回滾后,才能執行。 但是這種隔離級別一般比較少用。 容易造成性能上的問題。 效率比較低。
* 按效率劃分,從高到低
> 讀未提交 > 讀已提交 > 可重復讀 > 可串行化
* 按攔截程度 ,從高到底
> 可串行化 > 可重復讀 > 讀已提交 > 讀未提交
##事務總結
###需要掌握的
1. 在代碼里面會使用事務
conn.setAutoCommit(false);
conn.commit();
conn.rollback();
2. 事務只是針對連接連接對象,如果再開一個連接對象,那么那是默認的提交。
3. 事務是會自動提交的。
###需要了解的
####安全隱患
讀
臟讀
一個事務讀到了另一個事務未提交的數據
不可重復讀
一個事務讀到了另一個事務已提交的數據,造成前后兩次查詢結果不一致
幻讀
一個事務讀到了另一個事務insert的數據 ,造成前后查詢結果不一致 。
寫
丟失更新。
####隔離級別
讀未提交
> 引發問題: 臟讀
讀已提交
> 解決: 臟讀 , 引發: 不可重復讀
可重復讀
> 解決: 臟讀 、 不可重復讀 , 未解決: 幻讀
可串行化
> 解決: 臟讀、 不可重復讀 、 幻讀。
mySql 默認的隔離級別是 可重復讀
Oracle 默認的隔離級別是 讀已提交
###丟失更新
###解決丟失更新
* 悲觀鎖
> 可以在查詢的時候,加入 for update
數據庫的鎖的機制,排他鎖
* 樂觀鎖
> 要求程序員自己控制。
select @@tx_isolation查看現在的隔離級別(數據庫)
set session transaction isolation level read XXX;修改隔離等級(數據庫)
XXXX參數:uncommitted 讀未提交
committed 讀已經提交
read 重復度
serializable 可串行化
*/
?
數據庫連接代碼:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException;public class ConnLink { //數據庫的連接部分public String jdbc_driver="com.mysql.jdbc.Driver";public String jdbc_conn="jdbc:mysql://localhost:3306/test";public String user="root";public String pass="root";//返回連接函數的部分public Connection getConn() throws SQLException, ClassNotFoundException{//1.注冊驅動 Class.forName(jdbc_driver);//2.獲取連接Connection connection=DriverManager.getConnection(jdbc_conn,user,pass);return connection;}//釋放連接資源的部分public void relese(Connection conn,PreparedStatement pstmt) throws SQLException{if(pstmt!=null) pstmt.close();if(conn!=null) conn.close();} }
?
?
演示代碼:
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.servlet.jsp.tagext.TryCatchFinally;public class Transaction {static ConnLink connlink=new ConnLink();public static void main(String[] args) throws SQLException, ClassNotFoundException {Connection conn=null;PreparedStatement pstmt=null; try {//獲取數據庫的連接conn=connlink.getConn();//連接,事務默認就是自動提交的。 關閉自動提交。conn.setAutoCommit(false);//創建執行sql語句的對象3 書寫sql語句;String sqlString="update blank set money = money - ? where id = ?";//創建預處理對象pstmt = conn.prepareStatement(sqlString);//設置參數占位符pstmt.setInt(1, 100);pstmt.setInt(2,1); //執行語句 pstmt.executeUpdate();//以上部分是給用戶1扣100元,我們可以理解是用戶1給用戶2轉賬100元。所以下面要給用戶2增加100元//在此處設置一個錯誤,促使數據庫的事件回滾int a=10/0;String sqlString2="update blank set money = money + ? where id = ?";pstmt = conn.prepareStatement(sqlString2);pstmt.setInt(1, 100);pstmt.setInt(2,2); pstmt.executeUpdate();//由于上面的conn.setAutoCommit(false);設置成了flase所以此處需要用commit()進行提交 conn.commit();} catch (SQLException e) {// TODO: handle exception//這里是出現意外的情況就是轉賬失敗,那么怎么辦呢,直接取消所有的操作,實現數據庫的回滾。 conn.rollback();e.printStackTrace();}finally{connlink.relese(conn, pstmt);}} }
?