事务-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; } } |