JDBC中的事务开发

JDBC中的事务开发

在开发中,对数据库的多个表或者对一个表中的多条数据执行更新操作时要保证对多个更新操作要么同时成功,要么都不成功,这就涉及到对多个更新操作的事务管理问题了。

比如银行业务中的转账问题,A用户向B用户转账1000元,假设A用户和B用户的钱都存储在Account表,那么A用户向B用户转账时就涉及到同时更新Account表中的A用户的钱和B用户的钱。

1.1、在数据访问层(Dao)中处理事务

对于这样的同时更新一个表中的多条数据的操作,那么必须保证要么同时成功,要么都不成功,所以需要保证这两个update操作在同一个事务中进行。于是编写AccountDaoImpl实现AccountDao接口,进行转账操作:

转账操作涉及两个问题:

  • 根据卡号先查询相关账户
  • 修改账户信息
public interface AccountDao {
    //根据卡号查询账户
    Account queryAccountByCardID(int cardid) throws Exception;
​
    //修改账户
    void updateAccount(Account account) throws Exception;
​
​
}
public class AccountDaoImp implements AccountDao {
​
    @Override
    public Account queryAccountByCardID(int cardid) throws SQLException {
​
        QueryRunner runner = new QueryRunner();
        Connection connection = JDBCUtil.getConnection();
​
        Account account = runner.query(connection,"select *from account where cardid = ?",new BeanHandler<Account>(Account.class),cardid);
​
​
        return account;
    }
​
    @Override
    public void updateAccount(Account account) throws SQLException{
​
        QueryRunner runner = new QueryRunner();
        Connection connection = JDBCUtil.getConnection();
        runner.update(connection,"update account set balance=? where cardid=?",new Object[]{account.getBalance(),account.getCardid()});
    }
}

 

1.2、在service层处理事务 

然后我们在AccountService中写一个transfer方法,通过实现类重写方法:

 
public interface AccountService {
    void transfer(int fromCardId,int toCardId,int money);
}

 

public class AccountServiceImpl implements AccountService {
    @Override
    public void transfer(int fromCardId, int toCardId, int money) {
​
        AccountDaoImp accountDaoImp = new AccountDaoImp();
        //开启事务
        try {
            JDBCUtil.beginTransaction();
            //各种DML操作
            //张三.-1000  李四.+1000
            //先根据银行卡号查找账户
            Account fromCount = accountDaoImp.queryAccountByCardID(fromCardId);
            Account toAccount = accountDaoImp.queryAccountByCardID(toCardId);
​
            if(fromCount.getBalance()>money){
                int fromBalance = fromCount.getBalance()-1000;
                fromCount.setBalance(fromBalance);
                fromCount.setCardid(fromCardId);
                accountDaoImp.updateAccount(fromCount);
​
​
                int toBalance = toAccount.getBalance()+1000;
                toAccount.setBalance(toBalance);
                toAccount.setCardid(toCardId);
                accountDaoImp.updateAccount(toAccount);
​
                JDBCUtil.commitTransaction();
                System.out.println("转账成功");
            }else {
                System.out.println("转账失败");
            }
        } catch (SQLException e) {
            e.printStackTrace();
            try {
                JDBCUtil.rollbackTransaction();
                System.out.println("转账失败,数据回滚");
            } catch (Exception throwables) {
                throwables.printStackTrace();
            }finally {
                try {
                    JDBCUtil.closeTransaction();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
        }
    }
}

 

1.3、在上面service层中进行事务处理使用了ThreadLocal

ThreadLocal一个容器,向这个容器存储的对象,在当前线程范围内都可以取得出来,向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了

  ThreadLocal类的使用范例如下:

public class JDBCUtil {
 
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal();
    //获取数据库连接
    public static Connection getConnection() throws SQLException {
        Connection connection = threadLocal.get();
​
        if(connection == null){
            connection = DataSourceUtils.getDataSourceByC3P0XML().getConnection();
            
            //将数据库连接放入ThreadLocal容器
            threadLocal.set(connection);
        }
        return connection;
    }
​
    //开启事务
    public static void beginTransaction() throws SQLException{
        Connection connection = getConnection();
        connection.setAutoCommit(false);//手动提交开启事务
    }
​
    //正常,提交事务
    public static void commitTransaction() throws SQLException{
        Connection connection = getConnection();
        connection.commit();
    }
    //回滚事务
    public static void rollbackTransaction() throws SQLException{
        Connection connection = getConnection();
        if(connection!=null){
​
            connection.rollback();
​
        }
    }
​
    //关闭事务
    public static void closeTransaction() throws SQLException{
        Connection connection = getConnection();
        if(connection!=null){
​
            connection.close();
            threadLocal.remove();
            connection = null;
​
        }
    }
}

 

1.4、测试

 
package com.example.ApacheDbutils;
​
import com.example.ApacheDbutils.service.impl.AccountServiceImpl;
​
public class Test {
    public static void main(String[] args) {
        AccountServiceImpl accountService = new AccountServiceImpl();
 
 
        accountService.transfer(1,2,1000);
    }
}
 
 

1.5、基础知识总结

 

如果既要保证数据安全,又要保证性能,可以考虑ThreadLocal

ThreadLocal:可以为每个线程 复制一个副本。每个线程可以访问自己内部的副本。 别名 线程本地变量 set():给ThreadLocal中存放一个 变量 get():从ThreadLocal中获取变量(副本), remove();删除副本

对于数据库来说,一个连接 对应于一个事务 ,一个事务可以包含多个DML操作
​
Service(多个原子操作) -> Dao(原子操作)
​
转账
Service(转账)  ->  Dao(a.减少  b.增加)
​
Service     ->(connection ->psmst ->update)update       (connection ->psmst ->update)update : 如果给每个 dao操作 都创建一个connection,则多个dao操作对应于多个事务;
    但是一般来讲,一个业务(service) 中的多个dao操作 应该包含在一个事务中。
​
        ->解决,ThreadLocal, 在第一个dao操作时 真正的创建一个connection对象,然后在其他几次dao操作时,借助于ThreadLocal本身特性 自动将该connection复制多个(connection只创建了一个,因此该connection中的所有操作 必然对应于同一个事务; 并且ThreadLocal将connection在使用层面复制了多个,因此可以同时完成多个dao操作)
 

 

事务流程: 开启事务(将自动提交->手工提交) ->进行各种DML ->正常,将刚才所有DML全部提交 (全部成功) ->失败(异常),将刚才所有DML全部回滚(全部失败)

事务的操作 全部和连接Connection密切相关

posted @ 2021-02-16 17:25  aishimin  阅读(97)  评论(0)    收藏  举报