事務-Transaction
某些情況下我們希望對數據庫的某一操作要么整體成功,要么整體失敗,經典的例子就是支付寶提現。例如我們發起了支付寶到銀行卡的100元提現申請,我們希望的結果是支付寶余額減少100元,銀行卡余額增加100元,而不是支付寶的100元被扣除,而銀行卡的100元卻沒收到。也就是說,要么100元從支付寶扣除的同時銀行卡也會多出一百元,要么這次提現失敗支付寶的100元還在,銀行卡也沒有收到錢。支付寶扣錢和銀行卡收錢,這兩件事要么都成功要么都失敗。
事物的ACID特性:
滿足ACID特性的操作,我們可以說它是一個事物。
- 原子性:該操作是最小邏輯單元整體,已經不可分隔。
- 一致性:要么所有都執行,要么所有都不執行。
- 隔離性:多個事務相互隔離,互不影響。
- 持久性:事物的執行結果永久生效。
對事物的控制:
在JDBC中可以調用Connection對象的setAutoCommit(false)這個接口,將commit()之前的所有操作都看成是一個事物。同時,如果事務執行過程中發生異常,可以調用rollback()接口進行回滾到事務開始之前的狀態。
示例代碼:
下面代碼演示了將cjk的100元轉賬到ly的賬戶上:
package org.lyk.main;
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import org.apache.commons.dbcp2.BasicDataSource;
public class Main { public static String DBDRIVER = "com.mysql.jdbc.Driver"; public static String DB_URL = "jdbc:mysql://localhost:3306/mldn"; public static String USERNAME = "root"; public static String PASSWORD = "admin"; public static BasicDataSource bds = null;
public static void main(String[] args) { dbPoolInit(); transferAmount(); System.out.println("///Done~~~"); }
public static void dbPoolInit() { bds = new BasicDataSource(); bds.setDriverClassName(DBDRIVER); bds.setUrl(DB_URL); bds.setUsername(USERNAME); bds.setPassword(PASSWORD); }
public static boolean transferAmount() { boolean retVal = true;
String sql = "UPDATE user SET amount=? WHERE userid=?"; Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null;
try { conn = bds.getConnection(); conn.setAutoCommit(false); stmt = conn.prepareStatement(sql); stmt.setInt(1, 0); stmt.setString(2, "cjk"); stmt.execute(); stmt.setInt(1, 100); stmt.setString(2, "ly"); stmt.execute(); conn.commit(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); try { conn.rollback(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } finally { try { if(conn != null) conn.close(); if(stmt != null) stmt.close(); if(rs != null) rs.close(); } catch(Exception e) { //ignore all exceptions when closing... } }
return retVal; } }
|
事務斷點(Savepoint):
某些時候,我們對一個事物操作失敗,我們並不像回滾到最初狀態,而是回滾到事務開始后的某一個地方,這時我們可以使用斷點的方式讓事物回滾到指定的斷點(Savepoint)上.
示例代碼:
下面的代碼演示了如果cjk的100元轉賬到ly失敗的話,我們將這100元轉到cyx的賬戶上。(其中用手動跑出異常的方式模擬cjk到ly的轉賬失敗)
package org.lyk.main;
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Savepoint;
import org.apache.commons.dbcp2.BasicDataSource;
public class Main { public static String DBDRIVER = "com.mysql.jdbc.Driver"; public static String DB_URL = "jdbc:mysql://localhost:3306/mldn"; public static String USERNAME = "root"; public static String PASSWORD = "admin"; public static BasicDataSource bds = null;
public static void main(String[] args) { dbPoolInit(); transferAmount(); System.out.println("///Done~~~"); }
public static void dbPoolInit() { bds = new BasicDataSource(); bds.setDriverClassName(DBDRIVER); bds.setUrl(DB_URL); bds.setUsername(USERNAME); bds.setPassword(PASSWORD); }
public static boolean transferAmount() { boolean retVal = true;
String sql = "UPDATE user SET amount=? WHERE userid=?"; Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; Savepoint sp = null;
try { conn = bds.getConnection();
conn.setAutoCommit(false); stmt = conn.prepareStatement(sql); stmt.setInt(1, 0); stmt.setString(2, "cjk"); stmt.execute(); sp = conn.setSavepoint();
stmt.setInt(1, 100); stmt.setString(2, "ly"); stmt.execute(); throw new Exception(); } catch (Exception e) { e.printStackTrace(); try { conn.rollback(sp); stmt.setInt(1, 100); stmt.setString(2, "cyx"); stmt.execute(); conn.commit(); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } finally { try { if(conn != null) conn.close(); if(stmt != null) stmt.close(); if(rs != null) rs.close(); } catch(Exception e) { //ignore all exceptions when closing... } }
return retVal; } }
|