【事务】浅谈事务管理(传统方式+动态代理方式+AOP方式+Transcation注解方式)

【一】首先模拟一下原始的事务管理情况

【1】转账案例的过程

1-查询转出账户的余额
2-查询转入账户的余额
3-转出账户减钱
4-转入账户加钱

【2】模拟案例的原理

定义一个方法来手动关闭【自动提交】,然后在try-catch中手动进行提交,以此来模拟事务的管理

【3】事务管理工具类的代码分析

准备一个工具类来管理mysql的动作,通过connectionUtils对象获取一个mysql的连接,然后调用mysql的方法。
setAutoCommit(false):关闭mysql的自动提交
commit():手动提交sql
rollback():手动回滚sql

这些方法准备好了以后,我们就可以在需要进行事务管理的方法里面,手动的管理事务的流程。

@Component("txManager")//放入容器
public class TransactionManager {

    //引入连接线程的工具类
    @Autowired
    private ConnectionUtils connectionUtils;

    /**开启事务
     * @MethodName: beginTransaction
     * @Author: AllenSun
     * @Date: 2019/8/28 23:31
     */
    //存在弊端:beginTransaction方法名如果发生了改变,则所有引用的方法里都需要改,维护性特别差
    //解决方法:使用动态代理来降低耦合,添加周边功能,例如“添加事务管理”
    public void beginTransaction(){
        try {
            //1-手动关闭自动提交
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**提交事务
     * @MethodName: commit
     * @Author: AllenSun
     * @Date: 2019/8/28 23:32
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**回滚事务
     * @MethodName: rollback
     * @Author: AllenSun
     * @Date: 2019/8/28 23:33
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**释放连接
     * @MethodName: release
     * @Author: AllenSun
     * @Date: 2019/8/28 23:33
     */
    public void release(){
        try {
            //1-把线程换回线程池
            connectionUtils.getThreadConnection().close();
            //2-线程和连接进行解绑
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

【4】业务实现的代码分析

业务实现就是一个完整的转账流程。可以看到,在执行所有操作之前,先用
beginTransaction方法关闭mysql的自动提交,这样就可以在整个流程进行完后再手动提交。

当整个过程中出现异常报错的时候,就会在catch里面调用rollback方法进行回滚,这样就不会出现转账的差错了。

public void transfer(String sourceName, String targetName, Float money) {
    //给每一个方法都加上事务管理
    try {
        //1-开启事务
        txManager.beginTransaction();
        //2-执行操作
        //2-1-根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2-2-根据名称查询转入账户
        Account target = accountDao.findAccountByName(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);

        //3-提交事务
        txManager.commit();
        //4-返回结果
//            return allAccount;
    } catch (Exception e) {
        //5-回滚操作
        txManager.rollback();
        e.printStackTrace();
    } finally {
        //6-释放连接
        txManager.release();
    }
}

【5】这样的实现方法存在哪些问题

我们需要在业务实现中主动的去调用beginTransaction等方法,这样耦合度较高,万一beginTransaction方法的名字被改了,我们就要全局去找所有调用这个方法的地方,然后一个一个去改名字,维护的效率极低

【6】解决耦合度高的思路

可以使用动态代理来添加事务管理,添加周边功能,以此降低耦合度

【二】由事务管理引出动态代理实现

【1】动态代理的思路和上述方式思路的区别在哪

(1)上述方式很直接,哪个方法需要事务,我就在哪个方法里面加代码,把事务需要的逻辑都加在这个方法上,然后其他对象调用这个方法的时候就获取到一个带有完整事务的方法
(2)动态代理的实现逻辑是,方法里不带有任务事务管理的代码,然后把这个方法交给工厂类来代理,在工厂类里使用反射来获取这个方法,然后对这个方法进行代理改造,这个时候再加入事务管理的代码。当我们需要调用这个方法时候,不是直接去获取这个方法,而是用工厂类的对象去获取已经被代理改造后的方法

【2】业务实现的代码分析

可以看到,所有跟事务相关的代码都没有了,只有清清爽爽的业务逻辑代码。

public void transfer(String sourceName, String targetName, Float money) {
    System.out.println("开始转账...");
    //1-根据名称查询转出账户
    Account source = accountDao.findAccountByName(sourceName);
    //2-根据名称查询转入账户
    Account target = accountDao.findAccountByName(targetName);
    //3-转出账户减钱
    source.setMoney(source.getMoney() - money);
    //4-转入账户加钱
    target.setMoney(target.getMoney() + money);
    //5-更新转出账户
    accountDao.updateAccount(source);
    //出现异常,转账中断,导致钱转出去了,但是收钱的没有收到
    int i = 1 / 0;
    //6-更新转出账户
    accountDao.updateAccount(target);

}

【3】代理工厂的代码分析

注入了IAccountService类,来获取转账的方法transfer。注入了TransactionManager类来获取mysql操作的方法。

然后创建了代理方法getAccountService,返回的是一个IAccountService对象。在invoke方法将IAccountService的对象accountService代理

@Component("proxyAccountService")//代理对象放进容器
public class BeanFactory {

    @Autowired
    private IAccountService accountService;
    @Autowired
    private TransactionManager txManager;

    //用来获取Service代理对象
    public IAccountService getAccountService() {
        Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**添加事务的支持
                     * @MethodName: invoke
                     * @Author: AllenSun
                     * @Date: 2019/8/29 1:28
                     */
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                        Object returnValue=null;
                        try {
                            //1-开启事务
                            txManager.beginTransaction();
                            //2-执行操作
                            method.invoke(accountService, objects);
                            //3-提交事务
                            txManager.commit();
                            //4-返回结果
                            return returnValue;
                        } catch (Exception e) {
                            //5-回滚操作
                            txManager.rollback();
                            //写完以后发现:这时候报错误说没有返回值,直接加上一个抛出异常就行了
                            throw new RuntimeException(e);
                        } finally {
                            //6-释放连接
                            txManager.release();
                        }
                    }
                });
        return null;
    }
}

【4】实际使用时的代码分析

在调用IAccountService的transfer方法的时候,把代理对象proxyAccountService注入,然后调用的transfer方法就是代理之后的方法。

@RunWith(SpringJUnit4ClassRunner.class)//使用Junit提供的注解把原来的main方法替换了,换成spring提供的RunWith
@ContextConfiguration("classpath:applicationContext.xml")//找到IOC容器的位置,获取容器
public class AccountServicesTest {

    //依赖注入
    @Autowired
    @Qualifier("proxyAccountService")//把代理对象注入进来
    private IAccountService as;

    @Test
    public void testTransfer() {
        as.transfer("aaa", "bbb", 100f);
        System.out.println("转账结束!!!");
    }

}

【5】动态代理实现事务管理有哪些好处

1-【减少代码冗余】
把所有方法都会用到的事务管理逻辑代码抽取出来,在代理的时候再加上,可以减少代码的冗余
2-【降低代码耦合】
使用反射获取方法,然后加入事务管理的代码,这样是不考虑被代理方法的名称的,即使transfer方法改名字了,只要这个方法还是在IAccountService里,就可以被代理到。

(1.1)准备步骤

(1.1.1)准备pojo实体类
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;
    ...
(1.1.2)连接线程的工具类:用来从数据源中获取一个连接,并且实现和线程的绑定

@Component("connectionUtils")//这个类的对象放入IOC容器
public class ConnectionUtils {

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

    @Autowired
    private DataSource dataSource;

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

    /**
     * 线程和连接用完以后进行解绑
     *
     * @MethodName: removeConnection
     * @Author: AllenSun
     * @Date: 2019/8/28 23:40
     */
    public void removeConnection() {
        tl.remove();
    }
}
(1.1.3)事务管理相关的工具类

包含了四个方法:
1-开启事务
2-提交事务
3-回滚事务
4-释放连接

@Component("txManager")//放入容器
public class TransactionManager {

    //引入连接线程的工具类
    @Autowired
    private ConnectionUtils connectionUtils;

    /**开启事务
     * @MethodName: beginTransaction
     * @Author: AllenSun
     * @Date: 2019/8/28 23:31
     */
    //存在弊端:beginTransaction方法名如果发生了改变,则所有引用的方法里都需要改,维护性特别差
    //解决方法:使用动态代理来降低耦合,添加周边功能,例如“添加事务管理”
    public void beginTransaction(){
        try {
            //1-手动关闭自动提交
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**提交事务
     * @MethodName: commit
     * @Author: AllenSun
     * @Date: 2019/8/28 23:32
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**回滚事务
     * @MethodName: rollback
     * @Author: AllenSun
     * @Date: 2019/8/28 23:33
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**释放连接
     * @MethodName: release
     * @Author: AllenSun
     * @Date: 2019/8/28 23:33
     */
    public void release(){
        try {
            //1-把线程换回线程池
            connectionUtils.getThreadConnection().close();
            //2-线程和连接进行解绑
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

(1.2)编写步骤

(1.2.1)持久层Dao编写(接口没写)
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

    //    1-控制反转创建对象,然后依赖注入通过set方法注入(使用注解后记得删除set方法)
    @Autowired
    private QueryRunner runner;
    @Autowired
    private ConnectionUtils connectionUtils;

    /**
     * 查询所有
     *
     * @MethodName: findAllAccount
     * @Author: AllenSun
     * @Date: 2019/8/28 1:19
     */
    @Override
    public List<Account> findAllAccount() {

        try {
//          BeanListHandler
            return runner.query(connectionUtils.getThreadConnection(), "select * from account",
                    new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 根据id查询
     *
     * @MethodName: findAccountById
     * @Author: AllenSun
     * @Date: 2019/8/28 1:19
     */
    @Override
    public Account findAccountById(Integer accountId) {
        try {
//          改成BeanHandler
            return runner.query(connectionUtils.getThreadConnection(), "select * from account where id=? ",
                    new BeanHandler<Account>(Account.class),
                    accountId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 保存
     *
     * @MethodName: saveAccount
     * @Author: AllenSun
     * @Date: 2019/8/28 1:19
     */
    @Override
    public void saveAccount(Account account) {
        try {
//          删掉return
            runner.update(connectionUtils.getThreadConnection(), "insert into account(name,money) value(?,?) ", account.getName(), account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 更新
     *
     * @MethodName: updateAccount
     * @Author: AllenSun
     * @Date: 2019/8/28 1:19
     */
    @Override
    public void updateAccount(Account account) {
        try {
//          删掉return
            runner.update(connectionUtils.getThreadConnection(), "update account set name=?,money=? where id=?", account.getName(), account.getMoney(),
                    account.getId());
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 删除
     *
     * @MethodName: deleteAccount
     * @Author: AllenSun
     * @Date: 2019/8/28 1:19
     */
    @Override
    public void deleteAccount(Integer accountId) {
        try {
//          删掉return
            runner.update(connectionUtils.getThreadConnection(), "delete from account where id=?", accountId);
        } catch (SQLException e) {
            throw new RuntimeException();
        }

    }

    /**
     * 根据账户名称查询对应的账户
     *
     * @MethodName: findAccountByName
     * @Author: AllenSun
     * @Date: 2019/8/28 22:50
     */
    @Override
    public Account findAccountByName(String accountName) {
        try {
            List<Account> accounts = runner.query(connectionUtils.getThreadConnection(), "select * from account where name=? ",
                    new BeanListHandler<Account>(Account.class),
                    accountName);
            if (accounts == null || accounts.size() == 0) {
                return null;
            }
            if (accounts.size() > 1) {
                throw new RuntimeException("结果集不唯一,数据有问题!");
            }
            return accounts.get(0);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
(1.2.2)业务层Service编写(接口没写)
@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    //    1-调用持久层的对象accountDao,为持久层注入数据到业务层做准备,提供好set方法setAccountDao
    @Autowired
    private IAccountDao accountDao;

//    2-在业务层实现类的方法中,使用持久层的对象调用持久层接口中的方法,并且返回值给业务层

    /**
     * 转账方法
     * <p>
     * 解决事务管理的方法:
     * 需要使用ThreadLocal对象把Connection和当前线程绑定,从而使一个线程中只有一个能控制事务的对象
     * 事务控制应该都在业务层
     *
     * @MethodName: transfer
     * @Author: AllenSun
     * @Date: 2019/8/28 22:44
     */
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("开始转账...");
        //1-根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2-根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3-转出账户减钱
        source.setMoney(source.getMoney() - money);
        //4-转入账户加钱
        target.setMoney(target.getMoney() + money);
        //5-更新转出账户
        accountDao.updateAccount(source);
        //出现异常,转账中断,导致钱转出去了,但是收钱的没有收到
        int i = 1 / 0;
        //6-更新转出账户
        accountDao.updateAccount(target);

    }
}
(1.2.3)代理工厂编写

在service中的转账方法,只是按照正常流程进行转账,但是并没有添加事务管理。接下来就把Service注入到代理类中,由代理类给转账方法加上事务管理,使用的是JDK提供的动态管理类Proxy

/**
 * 用于创建Service的代理对象的工厂
 */
@Component("proxyAccountService")//代理对象放进容器
public class BeanFactory {

    @Autowired
    private IAccountService accountService;
    @Autowired
    private TransactionManager txManager;

    //用来获取Service代理对象
    public IAccountService getAccountService() {
        Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**添加事务的支持
                     * @MethodName: invoke
                     * @Author: AllenSun
                     * @Date: 2019/8/29 1:28
                     */
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                        Object returnValue=null;
                        try {
                            //1-开启事务
                            txManager.beginTransaction();
                            //2-执行操作
                            method.invoke(accountService, objects);
                            //3-提交事务
                            txManager.commit();
                            //4-返回结果
                            return returnValue;
                        } catch (Exception e) {
                            //5-回滚操作
                            txManager.rollback();
                            //写完以后发现:这时候报错误说没有返回值,直接加上一个抛出异常就行了
                            throw new RuntimeException(e);
                        } finally {
                            //6-释放连接
                            txManager.release();
                        }
                    }
                });
        return null;
    }
}

(1.3)测试步骤

@RunWith(SpringJUnit4ClassRunner.class)//使用Junit提供的注解把原来的main方法替换了,换成spring提供的RunWith
@ContextConfiguration("classpath:applicationContext.xml")//找到IOC容器的位置,获取容器
public class AccountServicesTest {

    //依赖注入
    @Autowired
    @Qualifier("proxyAccountService")//把代理对象注入进来
    private IAccountService as;

    @Test
    public void testTransfer() {
        as.transfer("aaa", "bbb", 100f);
        System.out.println("转账结束!!!");
    }

}

【三】由动态代理的逻辑引出使用AOP实现事务管理

(1)在事务管理的类里加上方法代理
首先使用@Pointcut找到切入点,目标就是service/impl文件夹下所有的类。然后使用arroundAdvice方法进行代理。

必须使用环绕通知。

@Component("txManager")//放入容器
@Aspect//说明这个类是一个切面类,同时加上一个
public class TransactionManager {

    //引入连接线程的工具类
    @Autowired
    private ConnectionUtils connectionUtils;

    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")//切入点表达式
    private void pt1() {
    }

    /**
     * 开启事务
     *
     * @MethodName: beginTransaction
     * @Author: AllenSun
     * @Date: 2019/8/28 23:31
     */
    public void beginTransaction() {
        try {
            //1-手动关闭自动提交
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     *
     * @MethodName: commit
     * @Author: AllenSun
     * @Date: 2019/8/28 23:32
     */
    public void commit() {
        try {
            connectionUtils.getThreadConnection().commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     *
     * @MethodName: rollback
     * @Author: AllenSun
     * @Date: 2019/8/28 23:33
     */
    public void rollback() {
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 释放连接
     *
     * @MethodName: release
     * @Author: AllenSun
     * @Date: 2019/8/28 23:33
     */
    public void release() {
        try {
            //1-把线程换回线程池
            connectionUtils.getThreadConnection().close();
            //2-线程和连接进行解绑
            connectionUtils.removeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 问题:
     *     使用注解AOP时会出现问题,无法进入提交Aotucommit=true
     * 分析:
     *     注解AOP的执行顺序是有问题的,会先进行最终通知(释放连接资源),再进行后置通知(提交数据连接)
     *     在最终通知时,释放连接资源,此时已经没有连接了
     *     接着调用后置通知,此时没有连接,它就会从数据源里再拿一个,并且把线程绑上去,
     *     虽然绑上去了,但是前置通知已经结束了,aotucommit已经为true,不能再次开启事务了
     *     所以,此时再调用后置通知提交业务就不成功了
     *
     * 打断点来分析:
     *     在开启事务之后,根本没有进入commit,而是直接来到最终通知释放资源
     *     然后再次进入开启事务,获得一个新的连接,再进入commit,但是其中是null,因为proceed执行方法后的连接已经被清除了
     *     新的连接里面没有任何执行操作,提交了也没用了
     * 解决:
     *     使用环绕通知
     *
     * 所以必须使用环绕通知才行
     *
     * @MethodName: arroundAdvice
     * @Author: AllenSun
     * @Date: 2019/8/31 15:18
     */
    @Around("pt1()")
    public Object arroundAdvice(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            //1-获取参数
            Object args = pjp.getArgs();
            //2-(一)前置通知:开启事务
            this.beginTransaction();
            //3-执行方法
            rtValue = pjp.proceed((Object[]) args);
            //4-(二)后置通知:提交事务
            this.commit();

            //7-返回值
            return rtValue;

            //exception是控制不了proceed的,所以要改成Throwable
        } catch (Throwable e) {
            //5-(三)异常通知:回滚事务
            this.rollback();
            throw new RuntimeException(e);
        } finally {
            //6-(四)最终通知:释放资源
            this.release();
        }
    }
}

【四】Spring事务

(1)Spring管理事务的方式有两种

  • 编程式事务(不推荐)
  • 声明式事务,在配置文件中配置(推荐)

(2)声明式事务有两种

  • 基于XML的声明式事务
  • 基于注解的声明式事务

(3)Spring事务中的隔离级别有5种
TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别.
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
  • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
  • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

(4)@Transactional(rollbackFor = Exception.class)注解了解
在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚

【五】注解声明式事务

【1】注解@Transactional的自调用失效问题

底层实现的原理还是Spring AOP 技术,而AOP结束使用的是动态代理。这就意味着对于静态(static)方法和非public方法,注解@Transactional都是失效的

【2】声明式事务

是基于AOP面向切面的,将具体业务与事务处理部分解耦,代码侵入性很低,所以在实际开发中声明式事务用的比较多。常用的是基于@Transactional 注解

【3】@Transactional 注解

(1)@Transactional注解可以作用于哪些地方?
@Transactional 可以作用在接口、类、类方法

  • @Transactional 可以作用在接口、类、类方法
  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息
  • 作用于方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务配置信息
@Transactional
@RestController
@RequestMapping
publicclass MybatisPlusController {
    @Autowired
    private CityInfoDictMapper cityInfoDictMapper;
    
    @Transactional(rollbackFor = Exception.class)
    @GetMapping("/test")
    public String test() throws Exception {
        CityInfoDict cityInfoDict = new CityInfoDict();
        cityInfoDict.setParentCityId(2);
        cityInfoDict.setCityName("2");
        cityInfoDict.setCityLevel("2");
        cityInfoDict.setCityCode("2");
        int insert = cityInfoDictMapper.insert(cityInfoDict);
        return insert + "";
    }
}

【4】@Transactional注解有哪些属性?

(1)propagation属性:事务的传播行为
(2)isolation 属性:事务的隔离级别
(3)timeout 属性:事务的超时时间
(4)readOnly 属性:是否为只读事务,默认值为 false
(5)rollbackFor 属性:指定能够触发事务回滚的异常类型,可以指定多个异常类型
(6)noRollbackFor属性:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型

【5】6种@Transactional失效场景

(1)@Transactional 应用在非 public 修饰的方法上
因为在Spring AOP 代理时,如上图所示 TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。

此方法会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。

(2)@Transactional 注解属性 propagation 设置错误
这种失效是由于配置错误,若是错误的配置以下三种 propagation,事务将不会发生回滚。

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

  • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

(3)@Transactional 注解属性 rollbackFor 设置错误
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
在这里插入图片描述

(4)同一个类中方法调用,导致@Transactional失效
开发中避免不了会对同一个类里面的方法调用,比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方。

那为啥会出现这种情况?其实这还是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理

(5)异常被你的 catch“吃了”导致@Transactional失效

    @Transactional
    private Integer A() throws Exception {
        int insert = 0;
        try {
            CityInfoDict cityInfoDict = new CityInfoDict();
            cityInfoDict.setCityName("2");
            cityInfoDict.setParentCityId(2);
            /**
             * A 插入字段为 2的数据
             */
            insert = cityInfoDictMapper.insert(cityInfoDict);
            /**
             * B 插入字段为 3的数据
             */
            b.insertB();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

(6)数据库引擎不支持事务
这种情况出现的概率并不高,事务能否生效数据库引擎是否支持事务是关键。常用的MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam,那事务就从根本上失效了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值