Java事務分爲:JDBC事務、JTA事務,容器事務。java
利用JDBC定義的java.sql.Connection接口中setAutoCommit(boolean)來達到自動提交和手動提交,只能侷限在同一個數據庫鏈接,不能跨庫。
切記:MySQL只有InnoDB纔有事務效果,其餘引擎設置了autoCommit無效。mysql
SQLsql
CREATE TABLE `test1` ( `id` bigint(1) NOT NULL DEFAULT 0 , `name` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , PRIMARY KEY (`id`) ) CREATE TABLE `test2` ( `id` bigint(1) NOT NULL DEFAULT 0 , `name` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL , PRIMARY KEY (`id`) )
Java數據庫
package com.mousycoder.server.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class JdbcNoTransaction { public final static String DB_DRIVER_CLASS = "com.mysql.jdbc.Driver"; public final static String DB_URL = "jdbc:mysql://xxx.mysql.rds.aliyuncs.com/xxx"; public final static String DB_USERNAME = "xx"; public final static String DB_PASSWORD = "xxx"; public static final String INSERT_TEST1 = "INSERT INTO test1(id,name) VALUES(?,?)"; public static final String INSERT_TEST2 = "INSERT INTO test2(id,name) VALUES(?,?)"; public static void main(String[] args) { Connection con = null; try { con = getConnection(); insertTest1(con); insertTest2(con); } catch (ClassNotFoundException|SQLException e ) { e.printStackTrace(); } finally { if (con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } } public static Connection getConnection() throws ClassNotFoundException, SQLException { Connection conn = null; Class.forName(DB_DRIVER_CLASS); conn = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD); return conn; } public static void insertTest1(Connection conn) throws SQLException { PreparedStatement stmt; stmt = conn.prepareStatement("INSERT INTO test1(id,name) VALUES(?,?)"); stmt.setInt(1, 1); stmt.setString(2, "1"); stmt.executeUpdate(); System.out.println("======insert into test1 successfully======"); stmt.close(); } public static void insertTest2(Connection conn) throws SQLException { PreparedStatement stmt; stmt = conn.prepareStatement("INSERT INTO test2(id,name) VALUES(?,?)"); stmt.setInt(1, 1); stmt.setString(2, "11"); // 故意長度超出 stmt.executeUpdate(); System.out.println("======insert into test2 successfully========"); stmt.close(); } }
控制檯輸出緩存
======insert into test1 successfully====== com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'name' at row 1 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3885) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3823) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2435) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2582) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2530) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1907) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2141) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2077) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2062) at com.mousycoder.server.jdbc.JdbcNoTransaction.insertTest2(JdbcNoTransaction.java:61) at com.mousycoder.server.jdbc.JdbcNoTransaction.main(JdbcNoTransaction.java:22)
結果:
表test1有數據,表test2裏沒有數據,可是咱們想讓test1,test2 要麼同時插入數據,要麼都不插數據,這個時候就須要用到事務了。
順便提一下,若是讓test1拋異常,那麼直接異常處理,結果是test1沒數據,test2也沒數據,可是這裏並非用到了事務,而是異常處理改變了程序走向。服務器
Java微信
package com.mousycoder.server.jdbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.SQLException; public class JdbcTransaction { public final static String DB_DRIVER_CLASS = "com.mysql.jdbc.Driver"; public final static String DB_URL = "jdbc:mysql://xx.mysql.rds.aliyuncs.com/xxx"; public final static String DB_USERNAME = "xxx"; public final static String DB_PASSWORD = "xxx"; public static final String INSERT_TEST1 = "INSERT INTO test1(id,name) VALUES(?,?)"; public static final String INSERT_TEST2 = "INSERT INTO test2(id,name) VALUES(?,?)"; public static void main(String[] args) { Connection con = null; try { con = getConnection(); con.setAutoCommit(false); insertTest1(con); insertTest2(con); con.commit(); System.out.println("=======JDBC Transaction commit==========="); } catch (ClassNotFoundException|SQLException e ) { try { con.rollback(); System.out .println("=======JDBC Transaction rolled back successfully======="); } catch (SQLException e1) { System.out.println("=======SQL Exception in rollback" + e1.getMessage()); // 回滾必要條件:1.同一個transaction // 2.connection未關,因此這裏要加異常處理 } e.printStackTrace(); } finally { if (con != null) { try { con.close(); } catch (SQLException e) { e.printStackTrace(); } } } } public static Connection getConnection() throws ClassNotFoundException, SQLException { Connection conn = null; Class.forName(DB_DRIVER_CLASS); conn = DriverManager.getConnection(DB_URL, DB_USERNAME, DB_PASSWORD); return conn; } public static void insertTest1(Connection conn) throws SQLException { PreparedStatement stmt; stmt = conn.prepareStatement("INSERT INTO test1(id,name) VALUES(?,?)"); stmt.setInt(1, 1); stmt.setString(2, "1"); stmt.executeUpdate(); System.out.println("======insert into test1 successfully======"); stmt.close(); } public static void insertTest2(Connection conn) throws SQLException { PreparedStatement stmt; stmt = conn.prepareStatement("INSERT INTO test2(id,name) VALUES(?,?)"); stmt.setInt(1, 1); stmt.setString(2, "11"); // 故意長度超出 stmt.executeUpdate(); System.out.println("======insert into test2 successfully========"); stmt.close(); } }
控制檯輸出session
======insert into test1 successfully====== =======JDBC Transaction rolled back successfully======= com.mysql.jdbc.MysqlDataTruncation: Data truncation: Data too long for column 'name' at row 1 at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3885) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3823) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2435) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2582) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2530) at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1907) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2141) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2077) at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2062) at com.mousycoder.server.jdbc.JdbcTransaction.insertTest2(JdbcTransaction.java:73) at com.mousycoder.server.jdbc.JdbcTransaction.main(JdbcTransaction.java:23)
結果:
表test1和表test2是沒有數據的,由於insertTest2方法拋出了異常,直接到異常處理,進行了手動的rollback(結束事務,回滾先前操做,釋放掉鎖),若是insertTest1()和insertTest2()都正常插入,則調用commit()方法(結束事務,提交先前改變,釋放掉鎖)oracle
com.mysql.jdbc.ConnectionImpl.rollback()ide
/** * The method rollback() drops all changes made since the previous * commit/rollback and releases any database locks currently held by the * Connection. * * @exception SQLException * if a database access error occurs * @see commit */ public void rollback() throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); try { if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { @Override void forEach(Extension each) throws SQLException { if (!((ConnectionLifecycleInterceptor) each).rollback()) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } // no-op if _relaxAutoCommit == true if (this.autoCommit && !getRelaxAutoCommit()) { throw SQLError.createSQLException("Can't call rollback when autocommit=true", SQLError.SQL_STATE_CONNECTION_NOT_OPEN, getExceptionInterceptor()); } else if (this.transactionsSupported) { try { rollbackNoChecks(); //真正執行rollback地方,發送rollback指令到數據庫 } catch (SQLException sqlEx) { // We ignore non-transactional tables if told to do so if (getIgnoreNonTxTables() && (sqlEx.getErrorCode() == SQLError.ER_WARNING_NOT_COMPLETE_ROLLBACK)) { return; } throw sqlEx; } } } catch (SQLException sqlException) { if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE.equals(sqlException.getSQLState())) { throw SQLError.createSQLException("Communications link failure during rollback(). Transaction resolution unknown.", SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor()); } throw sqlException; } finally { this.needsPing = this.getReconnectAtTxEnd(); } } }
解析:
只容許拿到鏈接互斥鎖的線程進入方法。
檢查鏈接是否被強制關閉,若被強制關閉,則拋出"No operations allowed after connection closed." 異常。
若是autoCommit且relaxAutoCommit爲false,則拋出"Can't call commit when autocommit=true"異常。
若是使用本地事務狀態以及MySql的版本號至少大於5.0.0而且事務在服務器上。
若是支持事務,則執行。
執行rollback。
com.mysql.jdbc.ConnectionImpl.commit()
public void commit() throws SQLException { synchronized (getConnectionMutex()) { checkClosed(); try { if (this.connectionLifecycleInterceptors != null) { IterateBlock<Extension> iter = new IterateBlock<Extension>(this.connectionLifecycleInterceptors.iterator()) { @Override void forEach(Extension each) throws SQLException { if (!((ConnectionLifecycleInterceptor) each).commit()) { this.stopIterating = true; } } }; iter.doForAll(); if (!iter.fullIteration()) { return; } } // no-op if _relaxAutoCommit == true if (this.autoCommit && !getRelaxAutoCommit()) { throw SQLError.createSQLException("Can't call commit when autocommit=true", getExceptionInterceptor()); } else if (this.transactionsSupported) { if (getUseLocalTransactionState() && versionMeetsMinimum(5, 0, 0)) { if (!this.io.inTransactionOnServer()) { return; // effectively a no-op } } execSQL(null, "commit", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false); } } catch (SQLException sqlException) { if (SQLError.SQL_STATE_COMMUNICATION_LINK_FAILURE.equals(sqlException.getSQLState())) { throw SQLError.createSQLException("Communications link failure during commit(). Transaction resolution unknown.", SQLError.SQL_STATE_TRANSACTION_RESOLUTION_UNKNOWN, getExceptionInterceptor()); } throw sqlException; } finally { this.needsPing = this.getReconnectAtTxEnd(); } } return; }
解析:
這段代碼與rollback()基本同樣,不一樣之處是commit()方法裏執行的是 execSQL(null, "commit", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false);
rollback()方法裏執行的是 execSQL(null, "rollback", -1, null, DEFAULT_RESULT_SET_TYPE, DEFAULT_RESULT_SET_CONCURRENCY, false, this.database, null, false);
執行事務期間涉及到的sql
/* mysql-connector-java-5.1.34 ( Revision: jess.balint@oracle.com-20141014163213-wqbwpf1ok2kvo1om ) */SHOW VARIABLES WHERE Variable_name ='language' OR Variable_name = 'net_write_timeout' OR Variable_name = 'interactive_timeout' OR Variable_name = 'wait_timeout' OR Variable_name = 'character_set_client' OR Variable_name = 'character_set_connection' OR Variable_name = 'character_set' OR Variable_name = 'character_set_server' OR Variable_name = 'tx_isolation' OR Variable_name = 'transaction_isolation' OR Variable_name = 'character_set_results' OR Variable_name = 'timezone' OR Variable_name = 'time_zone' OR Variable_name = 'system_time_zone' OR Variable_name = 'lower_case_table_names' OR Variable_name = 'max_allowed_packet' OR Variable_name = 'net_buffer_length' OR Variable_name = 'sql_mode' OR Variable_name = 'query_cache_type' OR Variable_name = 'query_cache_size' OR Variable_name = 'license' OR Variable_name = 'init_connect'
/* mysql-connector-java-5.1.34 ( Revision: jess.balint@oracle.com-20141014163213-wqbwpf1ok2kvo1om ) */SELECT @@session.auto_increment_increment
SET character_set_results = NULL
SET autocommit=1
SET sql_mode='STRICT_TRANS_TABLES'
SET autocommit=0
select @@session.tx_read_only
以上爲不徹底統計,jdbc包在執行一個真正的sql語句的時候,實際上會先執行一部分經常使用sql,把數據庫的一些信息給緩存起來。
oracle的commit詳細步驟
SGA中生成undo塊
SGA中生成了產生改變的數據塊和索引塊
SGA中生成前兩項的緩存redo信息
依賴於前三項產生的數據量大小以及操做所須要的時間,buffer中的數據可能已經有一部分輸出到磁盤
得到所需所有鎖
commit
爲事務生成一個SCN,SCN是oracle數據庫的一種計時信息,用以保證事務的順序性,同時還用於失敗恢復和保證數據庫的讀一致性和檢查點,不管什麼時候何人提交,SCN自動加1
將事務相關的未寫入redo log file 中的redo信息從redo log buffer寫入到redo log file ,這一步是真正的commit,這一步完成,才叫真正完成commit,事務條目從V$TRANSACTION中「刪除
V$LOCK中記錄的SESSION關於該事務的鎖會釋放,其餘須要這些鎖的事務被喚醒
執行塊清理,清理快頭保存的事務信息
oracle的rollback詳細步驟
撤銷已作的全部修改,從undo段讀回數據,逆向執行commit中的操做,並將undo條目標記爲已用,先前插入,則會刪除,先前更新,則會回滾取消更新,先前刪除,則回滾再次插入
釋放會話中全部的鎖,喚醒等待鎖。
mysql的commit詳細步驟
InnoDB每次提交事務都會刷新日誌innodb_log到磁盤,磁盤速度比較慢,不要頻繁提交事務
JTA是一種高層,與實現無關,與協議無關的API,應用程序和應用服務器可使用JTA來訪問事務
計劃用JTA界定事務,那麼須要實現javax.sql.XADataSource、javax.sql.XAConnection、java.sql.XAResource。
基於JTA,以及JNDI完成,容器負責事務的管理任務。
JDBC 事務侷限於一個數據庫鏈接,使用簡單。
JTA事務功能強大,能夠跨多個數據庫多個DAO,比較複雜。
容器事務,侷限於EJB使用。
MySql經常使用命令
查詢隔離級別select @@tx_isolation;
設置手動提交set autocommit=0 ;
查看當前事務自動提交模式select @@autocommit;
設置隔離級別set tx_isolation = 'READ-COMMITTED';
查詢表的狀態show table status like 'test1';
修改表的存儲引擎alter table test1 engine = INNODB
查看是否開啓日誌show variables like 'log_bin';
查看日誌狀態show master status;
感謝您的耐心閱讀,若是您發現文章中有一些沒表述清楚的,或者是不對的地方,請給我留言,您的鼓勵是做者寫做最大的動力,
若是您認爲本文質量不錯,讀後以爲收穫很大,不妨請我喝杯咖啡,讓我更有動力繼續寫出高質量的文章。
支付寶
微信
做 者 : @mousycoder
原文出處 : http://mousycoder.com/2016/02/15/explain-transaction-in-simple-language-2/
創做時間:2016-2-15
更新時間:2016-2-15