1. 程式人生 > >MySQL---當Java遇上MySQL⑤---單執行緒與多執行緒下的事務

MySQL---當Java遇上MySQL⑤---單執行緒與多執行緒下的事務

事務transaction

  • 原子性(atomicity):組成事務處理的語句形成了一個邏輯單元,不能只執行其中的一部分。
  • 一致性(consistency):在事務處理執行前後,資料庫是一致的(資料庫資料完整性約束)
  • 隔離性(isolcation):一個事務處理對另一個事務處理的影響。
  • 持續性(durability):事務處理的效果能夠被永久儲存下來 。
  • 一個事務只會有一個結果:要麼成功、要麼失敗

MySQL事務:開啟、提交、回滾

Start transaction;開始一個事務。

Commit;提交所做的修改。

Rollback;回滾所做的修改。如果在操作時出錯,應該從新開始一個事務。

資料庫表結構:

CREATE TABLE `tb_user` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(10) DEFAULT NULL,
  `password` VARCHAR(10) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

沒有事務下執行sql語句

演示沒有事務下執行 多條SQL語句

從上往下執行,遇到非法的sql語句就會丟擲異常,由於出現異常,接下來的sql語句將不會執行。

	@Test
	public void noTransaction() throws Exception {
		Connection con = ConnUtil.getConnection();
		Statement st = con.createStatement();
		//SQL 語句正確
		String sql = "insert into tb_user(username,password) value('Rose','123')";
		st.executeUpdate(sql); //正常執行
		//SQL 語句錯誤:id是INT型,並非 VARCHAR
		sql = "insert into tb_uservalue('u123','Rose','123')";
		st.executeUpdate(sql); //這裡會出現異常
		//SQL 語句正確
		sql = "insert into tb_user(username,password) value('Java','1234')";
		st.executeUpdate(sql); //由於上一句出現異常,這一句就不會執行。
		con.close();
	}

演示沒有事務下執行 批量處理

由於是批量處理,java與mysql之間的只有在executeBatch()時才進行通訊,而且把所有sql語句傳送過去,當mysql執行出現錯誤時,會把錯誤資訊封裝起來,等這次批量sql語句都執行完畢才把所有語句的執行結果反饋給java。

	@Test
	public void noTransaction2() throws Exception {
		Connection con = ConnUtil.getConnection();
		Statement st = con.createStatement();
		//SQL 語句正確
		String sql = "insert into tb_user(username,password) value('Rose','123')";
		st.addBatch(sql);
		//SQL 語句錯誤:id是INT型,並非 VARCHAR
		sql = "insert into tb_uservalue('u123','Rose','123')";
		st.addBatch(sql);
		//SQL 語句正確
		sql = "insert into tb_user(username,password) value('Java','1234')";
		st.addBatch(sql);
		/* 執行時下面這一句會出現異常,因為批量處理是隻有有一條語句出現問題,就會丟擲異常
		 * 但是語法正確的sql語句會照樣執行,不同於上面演示!!!
		 * 因為上面演示是 每條sql語句都是要進行一次通訊,在出現異常後,與mysql的連線就斷開了,
		 * 而 批量處理 則是一次性把所有sql語句傳送過去,即使異常也只是影響下一次的通訊。
		 */
		st.executeBatch();
		con.close();
	}

有事務的情況下執行 sql 語句

演示有事務下執行 多條SQL語句

因為開啟了事務,所以當第二條sql語句執行時,mysql反饋過來執行錯誤的資訊,excute()方法就丟擲一個異常,這時該異常被捕捉到,進入了catch塊中,通過 con.rollback()方法進行事務回滾。

	@Test
	public void transaction(){
		Connection con = ConnUtil.getConnection();
		try {
			con.setAutoCommit( false ); //開啟事務
			
			Statement st = con.createStatement();
			//SQL 語句正確
			String sql = "insert into tb_user(username,password) value('Rose','123')";
			st.executeUpdate(sql); //正常執行
			//SQL 語句錯誤:id是INT型,並非 VARCHAR
//			sql = "insert into tb_uservalue('u123','Rose','123')";
//			st.executeUpdate(sql); //這裡會出現異常
			//SQL 語句正確
			sql = "insert into tb_user(username,password) value('Java','1234')";
			st.executeUpdate(sql); 
			
			con.commit(); //提交事務
			System.out.println("事務完成...");
		} catch (SQLException e) {
			e.printStackTrace();
			try {
				con.rollback();
				System.out.println("事務回滾...");
			} catch (SQLException e1) {
				throw new RuntimeException( e1.getMessage(), e1);
			}
		} finally {
			if( con != null ) {
				try {
					//如果後序還有其他業務需要訪問資料庫的話,應該還原成事務自動提交
					//con.setAutoCommit( true );
					
					//如果沒有後序操作應該關閉連線
					con.close();
				} catch (SQLException e) {
					throw new RuntimeException(e.getMessage(), e);
				}
			}
		}
		
	}

演示有事務多執行緒下執行多條sql語句

由於獲取connection的工具是做成單例的形式,所以多執行緒下不同執行緒共享同一個connection連線,當一個執行緒的事務執行完畢後如果出現問題應該就回滾當前事務,而不影響其他執行緒,但是因為單例就會影響到其他執行緒的事務。解決方案:見連線池篇

	@Test
	public void demo1(){
		Connection con = ConnUtil.getConnection();
		try {
			con.setAutoCommit( false ); //開啟事務
			
			Statement st = con.createStatement();
			
			
			String sql = "insert into tb_user(username,password) value('Rose','123')";
			st.executeUpdate(sql); 
			
			//處理其他業務
			new MyThread(1).start();
			new MyThread(2).start();
			
			con.commit(); //提交事務
			System.out.println("主執行緒:事務完成...");
		} catch (SQLException e) {
			//e.printStackTrace();
			try {
				con.rollback(); //事務回滾
				System.out.println("主執行緒:事務回滾...");
			} catch (SQLException e1) {
				throw new RuntimeException( e1.getMessage(), e1);
			}
		} finally {
			if( con != null ) {
				try {
					//con.setAutoCommit( true ); //還原設定
					
					con.close();
				} catch (SQLException e) {
					throw new RuntimeException(e.getMessage(), e);
				}
			}
		}
	}
	
	class MyThread extends Thread{
		int num ;

		public MyThread(int num) {
			this.num = num;
		}

		@Override
		public void run() {

			Connection con = ConnUtil.getConnection();
			try {
				con.setAutoCommit( false ); //開啟事務
				
				Statement st = con.createStatement();
				
				String sql = "insert into tb_user(username,password) value('Rose"+num+"','123')";
				st.executeUpdate(sql); 
				
				con.commit(); //提交事務
				System.out.println( "子執行緒"+num+":事務完成...");
			} catch (SQLException e) {
				//e.printStackTrace();
				try {
					con.rollback(); //事務回滾
					System.out.println("子執行緒"+num+":事務回滾...");
				} catch (SQLException e1) {
					throw new RuntimeException( e1.getMessage(), e1);
				}
			} finally {
				if( con != null ) {
					try {
						//con.setAutoCommit( true ); //還原設定
						
						con.close();
					} catch (SQLException e) {
						throw new RuntimeException(e.getMessage(), e);
					}
				}
			}
		
		}
		
	}
}