文章目录

  • 写在前面
  • 壹 | 出现的问题
  • 贰 | 错误原因
  • 叁 | 解决办法
  • 肆 | 进行测试

写在前面

  • 项目结构如下所示:
  • 本文在 基于maven创建spring项目 上进行更改。


壹 | 出现的问题

  • 在写到转账案例时,代码中有一个地方出现了异常,但是数据库中的值没有回滚。
  • 出错代码:
IAccountService:
/**
 * 转账
 * @param sourceName
 * @param targetName
 * @param money
 */
void transfer(String sourceName,String targetName,Float money);

    =======================

AccountServiceImpl:

public void transfer(String sourceName, String targetName, Float money) {
    System.out.println("transfer");

    //2.1根据名称查询转出账户
    Account source = accountDao.findByName(sourceName);
    //2.2根据名称查询转入账户
    Account target = accountDao.findByName(targetName);
    //2.3转出账户减钱
    source.setMoney(source.getMoney()-money);
    //2.4转入账户加钱
    target.setMoney(target.getMoney()+money);
    //2.5更新转出账户
    accountDao.updateAccount(source);

	int i=1/0;      //异常处

    //2.6更新转入账户
    accountDao.updateAccount(target);

}
  • 在异常之前,程序正常运行,即数据库中的转出账户余额变少了;但因为除以0出现了异常,因此转入账户的余额并没有变多,即事务没有回滚。

贰 | 错误原因

  • 因为我们每次只需持久层方法都是独立的事务,导致无法出现事务控制。
  • 所以只要让他们只取用一个连接就可以避免这个问题。

叁 | 解决办法

  • 需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只要一个能控制事务的对象。
  • 需要添加的代码:
  • ConnectionUtils:
package com.itheima.utils;

import javax.sql.DataSource;
import java.sql.Connection;

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
 */
public class ConnectionUtils {

    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection() {
        try{
            //1.先从ThreadLocal上获取
            Connection conn = tl.get();
            //2.判断当前线程上是否有连接
            if (conn == null) {
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            //4.返回当前线程上的连接
            return conn;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}
  • TransactionManager:
package com.itheima.utils;

/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * 释放连接
     */
    public  void release(){
        try {
            connectionUtils.getThreadConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
  • BeanFactory:
package com.itheima.factory;

import com.itheima.service.IAccountService;
import com.itheima.utils.TransactionManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 用于创建Service的代理对象的工厂
 */
public class BeanFactory {

    private IAccountService accountService;

    private TransactionManager txManager;

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }


    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    /**
     * 获取Service代理对象
     * @return
     */
    public IAccountService getAccountService() {
        return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 添加事务的支持
                     *
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
//                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        if("test".equals(method.getName())){
                            return method.invoke(accountService,args);
                        }

                        Object rtValue = null;
                        try {
                            //1.开启事务
                            txManager.beginTransaction();
                            //2.执行操作
                            rtValue = method.invoke(accountService, args);
                            //3.提交事务
                            txManager.commit();
                            //4.返回结果
                            return rtValue;
                        } catch (Exception e) {
                            //5.回滚操作
                            txManager.rollback();
                            throw new RuntimeException(e);
                        } finally {
                            //6.释放连接
                            txManager.release();
                        }
                    }
                });

    }
}
  • 在AccountDaoImpl中的sql语句第一个参数前加“connectionUtils.getThreadConnection()”。如下所示:
public void updateAccount(Account account) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

肆 | 进行测试

  • 测试前:
  • 测试代码:
@Test
public void testTransfer(){
    //1.获取容易
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    //2.得到业务层对象
    IAccountService as = ac.getBean("accountService",IAccountService.class);

    as.transfer("zhangsan","lisi",100f);
}
  • 运行项目:
  • 数据库中张三的money没有改变,测试成功。