上一篇随笔记了一些有关JDBC事务管理的理论知识。这篇来看例子(主要怕一篇随笔装所有东西太长了然后分开呵呵)
一般讲事务管理的,都是拿转钱来当例子的,嗯没错我们这也是。
这个是数据库中的t_account表,装的就是额~可以理解成一个银行账号,就有id,用户名,还有用户的存款。
然后是一个简单的Java实体类:
package com.java.ws.transactionDemo; /** * 数据库的实体对应类 * 银行账号类 * @author 85060 * */ public class Account { private int id; private String accountName; private float accountBalance; public Account(int id, String accountName, float accountBalance) { this.id = id; this.accountBalance = accountBalance; this.accountName = accountName; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getAccountName() { return accountName; } public void setAccountName(String accountName) { this.accountName = accountName; } public float getAccountBalance() { return accountBalance; } public void setAccountBalance(float accountBalance) { this.accountBalance = accountBalance; } }
DbUtil类:是个封装了获取JDBC驱动还有连接代码的类,就不贴了,主要就是提供Connection还有关闭数据库连接和预处理。
然后来看 这个AccountInOutDao,这里我们不是很规范,逻辑代码也写在了这里:
package com.java.ws.transactionDemo; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import com.java.ws.util.DbUtil; public class AccountInOutDao { private static DbUtil dbUtil = new DbUtil(); /* new Account(1, "张三", 500); new Account(2, "李四", 1000);*/ public static int outMoney(Account user, int amount, Connection con) { String sql = "UPDATE t_account set accountBalance = accountBalance - ? where id = ?"; PreparedStatement pstmt = null; int result = 0; try { pstmt = con.prepareStatement(sql); pstmt.setInt(1, amount); pstmt.setInt(2, user.getId()); result = pstmt.executeUpdate();//返回执行的条数 } catch (SQLException e) { // TODO Auto-generated catch block System.out.println("在借钱出去的数据库操作里面出错"); e.printStackTrace(); } finally { dbUtil.close(pstmt, con); } return result; } public static int inMoney(Account user, int amount, Connection con) { String sql = "UPDATE t_account set accountBalance = accountBalance + ? where id = ?"; PreparedStatement pstmt = null; int result = 0; try { pstmt = con.prepareStatement(sql); pstmt.setInt(1, amount); pstmt.setInt(2, user.getId()); result = pstmt.executeUpdate();//返回执行的条数 } catch (SQLException e) { // TODO Auto-generated catch block System.out.println("在借钱出去的数据库操作里面出错"); e.printStackTrace(); } finally { dbUtil.close(pstmt, con); } return result; } public static void main(String[] args) {
Connection con = dbUtil.getCon();
System.out.println("张三要转钱200块了"); if(outMoney(new Account(1, "张三", 500), 200, con) != 0) { System.out.println("成功转钱"); } System.out.println("张三要转钱200块了"); if(inMoney(new Account(2, "李四", 1000), 200, con) != 0) { System.out.println("成功收钱"); } } }
操作后结果:
这里先是一个普通且正常运行的情况,就看拿钱也成功,取钱也成功。但如果我们现在人为地在转钱和收钱中间制造一个异常的话呢?
public static void main(String[] args) { System.out.println("张三要转钱200块了"); if(outMoney(new Account(1, "张三", 500), 200) != 0) { System.out.println("成功转钱"); } System.out.println(1/0); //制造了一个异常 System.out.println("张三要转钱200块了"); if(inMoney(new Account(2, "李四", 1000), 200) != 0) { System.out.println("成功收钱"); } }
结果就是,显而易见:
张三是转了两百块钱,然而李四啥都没收到。这要是在现实的银行系统中还得了hhh。
所以我们这里采用事务管理的方式,给个try catch块,如果抓到错误,就回滚。
public static void main(String[] args) { Connection con = null; try { con = dbUtil.getCon(); con.setAutoCommit(false); System.out.println("张三要转钱200块了"); if(outMoney(new Account(1, "张三", 500), 200, con) != 0) { System.out.println("成功转钱"); } System.out.println(1/0); //制造异常 System.out.println("张三要转钱200块了"); if(inMoney(new Account(2, "李四", 1000), 200, con) != 0) { System.out.println("成功收钱"); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println("转账失败!"); try { con.rollback();//有错误,回滚 System.out.println("刚刚走了回滚那一步"); } catch (SQLException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } finally { try { con.commit(); //最后提交,如果是没异常,则正常提交,如果有异常,就提交回滚那个位置前面的那些操作 } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } dbUtil.close(con); } }
结果:
数据库信息没变化:
刚刚我们采用全部回滚,也可以定点回滚:
Savepoint savePoint = null; savePoint = con.setSavepoint(); //记忆点 con.rollback(savePoint);//有错误,回滚到记忆点
附:
一开始我是在InMoney和OutMoney里面分别建立Connection,然后在main方法里面又搞了一个Connection用来回滚,想着说应该是可以吧,回滚到之前main这个con的初始情况。结果不行,要改成一个Connection才行。 后面百度相关信息,有看到当多个线程使用一个Connection的时候,会引起事务的混乱。。我想这也是为什么要有连接池这种技术的原因吧。