使用ThreadLocal实现转账事务
先我们准备此次实验所需jar包
commons-dbcp2-2.8.0.jar commons-dbutils-1.7.jar commons-logging-1.2.jar commons-pool2-2.9.0.jar mysql-connector-java-5.1.47.jar
既然是转账,我们新建一个账户类
package TrancatePack; public class Account { private int id ; private String name ; private int Balance; public Account() { } public Account(int id, String name, int balance) { this.id = id; this.name = name; Balance = balance; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getBalance() { return Balance; } public void setBalance(int balance) { Balance = balance; } }
同时,在数据库新建账户表,并插入两条数据
create table Account(id int,name varchar(20),balance int); insert into Account values(1,'ZS',10000),(2,'LS',10000);
然后我们编写新建Connection处理类
package TrancatePack; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import org.apache.commons.dbcp2.BasicDataSource; import org.apache.commons.dbcp2.BasicDataSourceFactory; public class ThreadLocalDemo { private static ThreadLocal<Connection> tlc ; public static Connection getConn() { if(tlc==null) { tlc = new ThreadLocal<Connection>(); } //首先从ThreadLocal获取连接 Connection conn = tlc.get(); if(conn == null) { //获取dbcp连接池 BasicDataSourceFactory bds = new BasicDataSourceFactory(); Properties p = new Properties(); //以文件以流的方式加载 InputStream in = new ThreadLocalDemo().getClass().getClassLoader().getSystemResourceAsStream("pro.properties"); try { p.load(in); //从连接池获取连接 conn = bds.createDataSource(p).getConnection(); //将连接放入ThreadLocal池 tlc.set(conn); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } return conn; } //开启事务 public static void beginTrancate() { Connection conn = getConn(); try { conn.setAutoCommit(false); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //正常结束事务 public static void commitTrancate() { Connection conn = getConn(); try { conn.commit(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //回滚事务 public static void rollbackTrancute() { Connection conn = getConn(); try { conn.rollback(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //关闭连接 public static void close() { Connection conn = getConn(); try { conn.close(); tlc.remove(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
然后我们创建账户处理类,首先新建一个接口,我们的DML处理均继承接口
package TrancatePack; public interface ServiceDao { int update(Account from) ; Account getAccount(int id); }
实现上面接口
package TrancatePack; import java.sql.Connection; import java.sql.SQLException; import java.util.List; import org.apache.commons.dbutils.DbUtils; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanListHandler; public class ServiceDaoImpl implements ServiceDao { //实现DButils对象 static QueryRunner qr = new QueryRunner(); //返回更新结果 @Override public int update(Account from) { //获取连接副本 Connection conn = new ThreadLocalDemo().getConn(); try { return qr.update(conn, "update Account set balance = ? where id=?", new Object[]{from.getBalance(),from.getId()} ); } catch (SQLException e) { e.printStackTrace(); return 0; } } //返回查询结果 @Override public Account getAccount(int id) { Account account = null; //获取连接副本 Connection conn = new ThreadLocalDemo().getConn(); try { List<Account> list = qr.query(conn, "select * from Account where id=?", new BeanListHandler<Account>(Account.class), id); for(Account account1:list) { account = account1; } return account; } catch (SQLException e) { e.printStackTrace(); return null; } } }
上面我们就准备好了实验数据,下面进行事务性测试
package TrancatePack; public class Transfer { public static void TransferTest() { try { //开启事务 ThreadLocalDemo.beginTrancate(); //获取两个账户 ServiceDao sd = new ServiceDaoImpl(); Account zs = sd.getAccount(1); Account ls = sd.getAccount(2); //开始转账 ZS ----> LS 1000 int Transaction_amount = 1000; if(zs.getBalance()>Transaction_amount) { //张三减去1000 zs.setBalance(zs.getBalance()-Transaction_amount); int a = sd.update(zs); //李四加上1000 ls.setBalance(ls.getBalance()+Transaction_amount); int b = sd.update(ls); if(a==0 || b==0) { throw new Exception("系统故障"); } ThreadLocalDemo.commitTrancate(); System.out.println("转账完成"); }else { System.out.println("余额不足"); } }catch (Exception e) { e.printStackTrace(); ThreadLocalDemo.rollbackTrancute(); ThreadLocalDemo.close(); } } public static void main(String[] args) { TransferTest(); } }
当上面转账事务操作过程中抛出任何异常,包括更新返回受影响行数为0,均会回滚事务,保持数据的稳定性。