使用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,均会回滚事务,保持数据的稳定性。

 

posted @ 2021-03-15 14:19  二五树  阅读(95)  评论(0编辑  收藏  举报