jdbc事务和事务的隔离级别
轉(zhuǎn)載自? ?jdbc事務(wù)和事務(wù)的隔離級(jí)別
在jdbc的使用中以最簡(jiǎn)單的jdbc的使用為例,說(shuō)明了jdbc的具體用法。然而在通常項(xiàng)目中,需要考慮更多內(nèi)容,例如事務(wù)。
事務(wù),在單個(gè)數(shù)據(jù)處理單元中,存在若干個(gè)數(shù)據(jù)處理,要么整體成功,要么整體失敗。事務(wù)需要滿足ACID屬性(原子性、一致性、隔離性和持久性)。
- 原子性:所謂原子性是指本次數(shù)據(jù)處理要么都提交、要么都不提交,即不能先提交一部分,然后處理其他的程序,然后接著提交未完成提交的剩余部分。概念類似于編程語(yǔ)言的原子操作。
- 一致性:所謂一致性是指數(shù)據(jù)庫(kù)數(shù)據(jù)由一個(gè)一致的狀態(tài)在提交事務(wù)后變?yōu)榱硗庖粋€(gè)一致的狀態(tài)。例如,用戶確認(rèn)到貨操作:確認(rèn)前,訂單狀態(tài)為待簽收、客戶積分為原始積分,此狀態(tài)為一致的狀態(tài);在客戶確認(rèn)到后后,訂單狀態(tài)為已完成、客戶積分增加本次消費(fèi)的積分,這兩個(gè)狀態(tài)為一致?tīng)顟B(tài)。不能出現(xiàn),訂單狀態(tài)為待簽收,客戶積分增加或者訂單狀態(tài)為已完成,客戶積分未增加的狀態(tài),這兩種均為不一致的情況。一致性與原子性息息相關(guān)。
- 隔離性:所謂隔離性是指事物與事務(wù)之間的隔離,即在事務(wù)提交完成前,其他事務(wù)與未完成事務(wù)的數(shù)據(jù)中間狀態(tài)訪問(wèn)權(quán)限,具體可通過(guò)設(shè)置隔離級(jí)別來(lái)控制。
- 持久性:所謂持久性是指本次事務(wù)提交完成或者回滾完成均為持久的修改,除非其他事務(wù)進(jìn)行操作否則數(shù)據(jù)庫(kù)數(shù)據(jù)不能發(fā)生改變。
本文重點(diǎn)描述事物隔離性及使用方法。
要詳細(xì)說(shuō)明數(shù)據(jù)庫(kù)隔離級(jí)別,需要先對(duì)數(shù)據(jù)庫(kù)并發(fā)事務(wù)可能出現(xiàn)的幾種狀態(tài)進(jìn)行說(shuō)明:
1、讀臟:一個(gè)事務(wù)讀取另外一個(gè)事務(wù)尚未提交的數(shù)據(jù)。如下圖,線程thread1在事務(wù)中在time1時(shí)刻向庫(kù)表中新增一條數(shù)據(jù)‘test’并在time3時(shí)刻回滾數(shù)據(jù);線程thread2在time2時(shí)刻讀取,若thread2讀取到‘test’,則為讀臟。
??
2、不可重新讀:其他事務(wù)的操作導(dǎo)致某個(gè)事務(wù)兩次讀取數(shù)據(jù)不一致。如下圖,線程thread1在事務(wù)中time1時(shí)刻將數(shù)據(jù)庫(kù)中‘test’更新為‘00’,并在time3時(shí)刻提交;thread2在一個(gè)事務(wù)中分別在time2和time4兩個(gè)時(shí)刻讀取這條記錄,若兩次讀取結(jié)果不同則為不可重讀。(注意:1.不可重讀針對(duì)已經(jīng)提交的數(shù)據(jù)。2.兩次或多次讀取同一條數(shù)據(jù)。)
?
3、幻讀:其他事務(wù)的數(shù)據(jù)操作導(dǎo)致某個(gè)事務(wù)兩次讀取數(shù)據(jù)數(shù)量不一致。如下圖,線程thread1在事務(wù)中time1時(shí)刻向數(shù)據(jù)庫(kù)中新增‘00’,并在time3時(shí)刻提交;thread2在一個(gè)事務(wù)中分別在time2和time4兩個(gè)時(shí)刻掃描庫(kù)表,若兩次讀取結(jié)果不同則為幻讀。(注意:1.幻讀針對(duì)已經(jīng)提交的數(shù)據(jù)。2.兩次或多次讀取不同行數(shù)據(jù),數(shù)量上新增或減少。)
針對(duì)上訴3中事務(wù)并發(fā)情況,jdbc定義了5中事務(wù)隔離級(jí)別:
- TRANSACTION_NONE 無(wú)事務(wù)
- TRANSACTION_READ_UNCOMMITTED 允許讀臟,不可重讀,幻讀。
- TRANSACTION_READ_COMMITTED 直譯為僅允許讀取已提交的數(shù)據(jù),即不能讀臟,但是可能發(fā)生不可重讀和幻讀。
- TRANSACTION_REPEATABLE_READ 不可讀臟,保證同一事務(wù)重復(fù)讀取相同數(shù)據(jù),但是可能發(fā)生幻讀。
- TRANSACTION_SERIALIZABLE 直譯為串行事務(wù),保證不讀臟,可重復(fù)讀,不可幻讀,事務(wù)隔離級(jí)別最高。
**> 注意:
- 隔離級(jí)別對(duì)當(dāng)前事務(wù)有效,例如若當(dāng)前事務(wù)設(shè)置為TRANSACTION_READ_UNCOMMITTED,則允許當(dāng)前事務(wù)對(duì)其他事務(wù)未提交的數(shù)據(jù)進(jìn)行讀臟,而非其他事務(wù)可對(duì)當(dāng)前事務(wù)未提交的數(shù)據(jù)讀臟。
- 部分?jǐn)?shù)據(jù)庫(kù)不支持TRANSACTION_NONE,例如mysql。
- 在TRANSACTION_SERIALIZABLE 隔離級(jí)別下,為先執(zhí)行DML更新,再執(zhí)行查詢,此處為實(shí)驗(yàn)的結(jié)論。
- 若未顯示設(shè)置隔離級(jí)別,jdbc將采用數(shù)據(jù)庫(kù)默認(rèn)隔離級(jí)別。文中實(shí)驗(yàn)數(shù)據(jù)庫(kù)的默認(rèn)隔離級(jí)別為:**
以下將分別在各種事務(wù)隔離級(jí)別下,通過(guò)設(shè)置事務(wù)內(nèi)訪問(wèn)間隔時(shí)間,模擬讀臟、不可重讀、幻讀。
建立庫(kù)表腳本如下:
CREATE TABLE `t_dict` (`dict_type` varchar(255) DEFAULT NULL,`dict_code` varchar(255) DEFAULT NULL,`dict_name` varchar(255) DEFAULT NULL,`dict_remark` varchar(255) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;主線程,用于建立數(shù)據(jù)庫(kù)連接、設(shè)置隔離級(jí)別、打印輸出等
package DBTest;import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.List;public class DBTest {private String url ;private String user;private String password;/*** 創(chuàng)建數(shù)據(jù)連接* @return*/private Connection getCon(){Connection con = null;try{Class.forName("com.mysql.jdbc.Driver");url = "jdbc:mysql://localhost:3306/twork";user = "root";password = "root";con = DriverManager.getConnection(url, user, password);}catch (Exception e){e.printStackTrace();try {con.close();} catch (SQLException e1) {e1.printStackTrace();}}return con;}/*** 通過(guò)鏈接獲取聲明* @param con* @return*/private Statement getStat(Connection con){Statement state = null;try{state = con.createStatement();}catch(Exception e){e.printStackTrace();}return state;}/*** 打印數(shù)據(jù)庫(kù)所有數(shù)據(jù)*/public void selectAll(int transactionType){Connection con = null;Statement state = null;ResultSet rs = null;try{con = getCon();if(transactionType >= 0 ){con.setTransactionIsolation(transactionType);}System.out.println("-------------當(dāng)前事務(wù)隔離級(jí)別為:"+con.getTransactionIsolation()+"-------------");state = getStat(con);rs = state.executeQuery("select * from t_dict");ResultSetMetaData rsmd = rs.getMetaData();for(int i = 1;i<= rsmd.getColumnCount() ;i++){System.out.print(rsmd.getColumnName(i)+"| ");}System.out.println();System.out.println("-------------------------------------------");//打印所有行while(rs.next()){for(int i = 1;i<= rsmd.getColumnCount() ;i++){System.out.print(rs.getString(i)+"| ");}System.out.println();}}catch (Exception e){try {con.rollback();} catch (SQLException e1) {e1.printStackTrace();}e.printStackTrace();}finally {try {if(rs != null){rs.close();}} catch (SQLException e) {e.printStackTrace();}try {if(state != null){state.close();}} catch (Exception e){}try {if(con != null){con.close();}} catch (SQLException e) {e.printStackTrace();}}}/*** 新增一行* @param needExcepition* @param sleepTimes* @param values* @return*/public int insertOne(int needExcepition,int sleepTimes, List<String> values){Connection con = getCon();PreparedStatement pre = null;String sql = "INSERT INTO t_dict (dict_type, dict_code, dict_name, dict_remark) VALUES (?, ?, ?, ?)";int res = 0;try {con.setAutoCommit(false);pre = con.prepareStatement(sql);for(int i = 0; i < values.size() ;i++){pre.setString(i+1, values.get(i));}Thread.sleep(sleepTimes);System.out.println("before execute");res = pre.executeUpdate();System.out.println("after execute");Thread.sleep(sleepTimes);int i = 1/needExcepition;System.out.println("before commit");con.commit();System.out.println("after commit");} catch (Exception e) {try {System.out.println("before roll back");con.rollback();System.out.println("after roll back");res = 0;} catch (SQLException e1) {e1.printStackTrace();}e.printStackTrace();} finally {try {if(pre != null){pre.close();}} catch (Exception e){}try {if(con != null){con.close();}} catch (SQLException e) {e.printStackTrace();}}return res;}/*** 間隔一定時(shí)間讀取多次* @param dictType 要去讀取的數(shù)據(jù)類型* @param sleepTimes 每次讀取之間的間隔時(shí)間* @param printTimes 打印次數(shù)* @param transactionType 事務(wù)隔離級(jí)別*/private void printMultiple(String dictType,int sleepTimes,int printTimes, int transactionType){Connection con = null;Statement state = null;ResultSet rs = null;try{con = getCon();con.setAutoCommit(false);if(transactionType >= 0){con.setTransactionIsolation(transactionType);}System.out.println("-------------當(dāng)前事務(wù)隔離級(jí)別為:"+con.getTransactionIsolation()+"-------------");state = getStat(con);for (int j = 0; j < printTimes; j++) {Thread.sleep(sleepTimes);rs = state.executeQuery("select * from t_dict where dict_type = '"+dictType+"' ");ResultSetMetaData rsmd = rs.getMetaData();System.out.println("第"+(j+1)+"次讀取");for(int i = 1;i<= rsmd.getColumnCount() ;i++){System.out.print(rsmd.getColumnName(i)+"| ");}System.out.println();System.out.println("-------------------------------------------");while(rs.next()){for(int i = 1;i<= rsmd.getColumnCount() ;i++){System.out.print(rs.getString(i)+"| ");}System.out.println();} }con.commit();}catch (Exception e){try {con.rollback();} catch (SQLException e1) {e1.printStackTrace();}e.printStackTrace();}finally {try {if(rs != null){rs.close();}} catch (SQLException e) {e.printStackTrace();}try {if(state != null){state.close();}} catch (Exception e){}try {if(con != null){con.close();}} catch (SQLException e) {e.printStackTrace();}}}/*** 更改一條數(shù)據(jù)的內(nèi)容* @param dict_type* @param sleepTimes* @param values* @return*/public int updateOne(String dict_type,int sleepTimes, List<String> values){Connection con = null;PreparedStatement pre = null;String sql = "UPDATE t_dict SET dict_code = ?, dict_name = ?, dict_remark = ? WHERE dict_type ='"+dict_type+"'";int res = 0;try {con = getCon();con.setAutoCommit(false);con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);pre = con.prepareStatement(sql);for(int i = 0; i < values.size() ;i++){pre.setString(i+1, values.get(i));}Thread.sleep(sleepTimes);System.out.println("before execute ");res = pre.executeUpdate();System.out.println("after execute ");Thread.sleep(sleepTimes);System.out.println("before commit");con.commit();System.out.println("after commit");} catch (Exception e) {try {con.rollback();} catch (SQLException e1) {e1.printStackTrace();}e.printStackTrace();} finally {try {if(pre != null){pre.close();}} catch (Exception e){}try {if(con != null){con.close();}} catch (SQLException e) {e.printStackTrace();}}return res;}/*** @param transType*/public void testTransaction(int transType){intDate();System.out.println("-------------------讀臟模擬---------------------");testDirty(transType);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------------不可重讀模擬------------------");testRepeat(transType);try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------------幻讀模擬----------------------");testTrick(transType);}/*** 初始化數(shù)據(jù)*/private void intDate(){System.out.println("------------初始化數(shù)據(jù) start-------------");Connection con = getCon();Statement pre = null;String sqlDelete = "delete from t_dict";String sqlInsert = "INSERT INTO `twork`.`t_dict` (`dict_type`, `dict_code`, `dict_name`, `dict_remark`) VALUES ('type0', '00', 'type00', 'type00')";try {con.setAutoCommit(false);pre = con.createStatement();pre.execute(sqlDelete);pre.execute(sqlInsert);con.commit();} catch (Exception e) {try {con.rollback();} catch (SQLException e1) {e1.printStackTrace();}e.printStackTrace();} finally {try {if(pre != null){pre.close();}} catch (Exception e){e.printStackTrace();}try {if(con != null){con.close();}} catch (SQLException e) {e.printStackTrace();}}System.out.println("------------初始化數(shù)據(jù) end-------------");}/*** 模擬讀臟,拋出未捕獲異常,插入數(shù)據(jù)不提交*/public void testDirty(int transactionType){List<String> list = new ArrayList<String>();list.add("type1");list.add("11");list.add("type11");list.add("type11");TestThread testThread = new TestThread("insert",0,300,list);Thread thread = new Thread(testThread);thread.start();try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}selectAll(transactionType);}/*** 模擬幻讀,第N次讀取多出數(shù)據(jù)*/public void testTrick(int transactionType){List<String> list = new ArrayList<String>();list.add("type0");list.add("11");list.add("type11");list.add("type11");//執(zhí)行插入,不產(chǎn)生異常TestThread testThread = new TestThread("insert",1,400,list);Thread thread = new Thread(testThread);thread.start();//打印兩次printMultiple("type0", 300,4,transactionType);}/*** 模擬不可重讀,多次讀取同一條記錄,記錄被更改*/public void testRepeat(int transactionType){List<String> list = new ArrayList<String>();list.add("type0");list.add("11");list.add("type11");list.add("type11");//執(zhí)行插入,不產(chǎn)生異常TestThread testThread = new TestThread("update",1,400,list);Thread thread = new Thread(testThread);thread.start();//打印4次printMultiple("type0", 300,4,transactionType);}public static void main(String[] args){DBTest dbTest = new DBTest();/*分別執(zhí)行下面的方法,即可模擬各個(gè)隔離級(jí)別下,線程并發(fā)事務(wù)間的訪問(wèn)結(jié)果*/System.out.println(" -----------------------TRANSACTION_READ_UNCOMMITTED test start------------------------");dbTest.testTransaction(Connection.TRANSACTION_READ_UNCOMMITTED);// System.out.println(" -----------------------TRANSACTION_READ_COMMITTED test start------------------------"); // dbTest.testTransaction(Connection.TRANSACTION_READ_COMMITTED); // // System.out.println(" -----------------------TRANSACTION_REPEATABLE_READ test start------------------------"); // dbTest.testTransaction(Connection.TRANSACTION_REPEATABLE_READ); // // System.out.println(" -----------------------TRANSACTION_SERIALIZABLE test start------------------------"); // dbTest.testTransaction(Connection.TRANSACTION_SERIALIZABLE); // // System.out.println(" -----------------------default test start------------------------"); // dbTest.testTransaction(-1);}}并發(fā)線程,調(diào)用讀取方法
package DBTest;import java.util.ArrayList; import java.util.List;/** * Created by ygl on 2016/5/1. */ public class TestThread implements Runnable {int needException = 1;int sleepTimes = 0;List<String> list = new ArrayList<String>();String method = "";DBTest dbTest = new DBTest();/*** @param method insert 或 update* @param needException 是否需要拋出異常,0拋出異常,1不拋出異常* @param sleepTimes 線程睡眠時(shí)間(毫秒)* @param list 更新數(shù)據(jù)庫(kù)的數(shù)據(jù),當(dāng)method為update時(shí),list的第一個(gè)元素為條件,其他為更新內(nèi)容*/public TestThread(String method,int needException, int sleepTimes , List<String> list){this.needException = needException;this.sleepTimes = sleepTimes;this.list = list;this.method = method;}public void run(){if("insert".equals(method)){insert();} else if("update".equals(method)){update();}}private void insert(){int res = dbTest.insertOne(needException, sleepTimes, list);if(res == 1){System.out.println("insert success");}else{System.out.println("insert fail");}}private void update(){String updateKey = list.get(0);list.remove(0);int res = dbTest.updateOne(updateKey, sleepTimes, list);if(res == 1){System.out.println("update success");}else{System.out.println("update fail");}} }讀者可以使用上述程序分別測(cè)試,這里僅以TRANSACTION_READ_UNCOMMITTED為例,輸出結(jié)果為:
-----------------------TRANSACTION_READ_UNCOMMITTED test start------------------------ ------------初始化數(shù)據(jù) start-------------------------初始化數(shù)據(jù) end------------- -------------------讀臟模擬---------------------before execute after execute-------------當(dāng)前事務(wù)隔離級(jí)別為:1------------- dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 00| type00| type00| type1| 11| type11| type11| before roll back after roll back java.lang.ArithmeticException: / by zeroat DBTest.DBTest.insertOne(DBTest.java:141)at DBTest.TestThread.insert(TestThread.java:41)at DBTest.TestThread.run(TestThread.java:34)at java.lang.Thread.run(Unknown Source) insert fail -------------------不可重讀模擬-------------------------------當(dāng)前事務(wù)隔離級(jí)別為:1------------- 第1次讀取 dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 00| type00| type00| before execute after execute 第2次讀取 dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 11| type11| type11| before commit after commit update success 第3次讀取 dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 11| type11| type11| 第4次讀取 dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 11| type11| type11| -------------------幻讀模擬-----------------------------------當(dāng)前事務(wù)隔離級(jí)別為:1------------- 第1次讀取 dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 11| type11| type11| before execute after execute 第2次讀取 dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 11| type11| type11| type0| 11| type11| type11| before commit 第3次讀取 dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 11| type11| type11| type0| 11| type11| type11| after commit insert success 第4次讀取 dict_type| dict_code| dict_name| dict_remark| ------------------------------------------- type0| 11| type11| type11| type0| 11| type11| type11|--------------------- 本文來(lái)自 光禮同學(xué) 的CSDN 博客 ,全文地址請(qǐng)點(diǎn)擊:https://blog.csdn.net/crow_feiyu/article/details/51305826?utm_source=copy
總結(jié)
以上是生活随笔為你收集整理的jdbc事务和事务的隔离级别的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 未受信任的企业级开发者怎么解决
- 下一篇: 为什么手机亮度调到最高还是很暗