事务详解+Spring事务失效场景

事务详解



前言

提示:这里可以添加本文要记录的大概内容:
例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。


提示:以下是本篇文章正文内容,下面案例可供参考

一、事务概念

事务是由一组SQL语句组成的逻辑处理单元。
事务特性
事务具有以下4个特性,简称为事务ACID属性。

ACID属性含义
原子性(Atomicity)事务是一个原子操作单元,其对数据的修改,要么全部成功,要么全部失败
一致性(Consistent)在事务开始和完成时,数据都必须保持一致状态
隔离性(Isolation)数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
持久性(Durable)事务完成之后,对于数据的修改是永久的

并发事务处理带来的问题

问题含义
丢失更新(Lost Update)当两个或多个事务选择同一行,最初的事务修改的值,会被后面的事务修改的值覆盖
脏读(Drity Reads)当一个事务正在访问数据,并对数据进行了修改,而这种修改还没有提交到数据库中,这时另一个事务也访问了这个数据,然后使用了这个数据。
不可重复读(Non-Repeatable Reads)一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,发现和以前读出的数据不一致
幻读(PhantomReads)一个事务按照相同的查询条件重新读取以前查询过的数据,却发现其他事务插入了满足其查询条件的新数据

事务隔离级别

隔离界别丢失更新脏读不可重复读幻读
读未提交(Read unCommiteed)×
读已提交(Read committed)××
可重复读(Repeatable read)×××
可序列化(Serializable)××××

二、本地事务

什么是本地事务呢?
其实就是一个项目发生对数据库事务的操作,本质上是依赖于数据库的事务管理。

二、Spring事务

1,JDBC事务

    public static void main(String[] args) {
        Connection connection = null;
        try {
            //SPI 注册驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
             connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            //取消MYSQL事务自动提交
            connection.setAutoCommit(false);
            PreparedStatement ps = connection.prepareStatement("UPDATE user set userNickName=? where id=?");
            ps.setString(1, "2222");
            ps.setString(2, "1");
            ps.executeUpdate();
            int a = 5 / 0;
            connection.commit();
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
            try {
                connection.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
        }
    }

JDBC事务依赖于数据库提供的事务特性。如果使用MYISAM,则不支持。

2,事务的传播行为

spring中事务的定义 TransactionDefinition

public interface TransactionDefinition {
	int PROPAGATION_REQUIRED = 0;
	int PROPAGATION_SUPPORTS = 1;
	int PROPAGATION_MANDATORY = 2;
	int PROPAGATION_REQUIRES_NEW = 3;
	int PROPAGATION_NOT_SUPPORTED = 4;
	int PROPAGATION_NEVER = 5;
	int PROPAGATION_NESTED = 6;
传播行为含义
PROPAGATION_REQUIRED = 0支持当前事务; 如果不存在,则创建一个新的
PROPAGATION_SUPPORTS = 1支持当前事务; 如果不存在则以非事务方式执行
PROPAGATION_MANDATORY = 2支持当前事务; 如果当前事务不存在,则抛出异常
PROPAGATION_REQUIRES_NEW = 3创建一个新事务,如果存在则暂停当前事务
PROPAGATION_NOT_SUPPORTED = 4不支持当前事务; 而是始终以非事务方式执行
PROPAGATION_NEVER = 5不支持当前事务; 如果当前事务存在则抛出异常务
PROPAGATION_NESTED = 6如果当前事务存在,则在嵌套事务中执行,否则行为类似于PROPAGATION_REQUIRED

3,编程式事务

@RestController
public class BsCityController {

    @Autowired
    private IBsCityService bsCityService;
    @Autowired
    private PlatformTransactionManager txManager;
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @RequestMapping("/test")
    public Object test(String id) {
//      return   bsCityService.getById(id);
        //定义事务的隔离级别,传播行为
        DefaultTransactionDefinition info = new DefaultTransactionDefinition();
        //设置隔离级别,可重复读
        info.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ);
        //设置传播行为,支持当前事务,如果不存在,创建一个新的事务
        info.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

        //事务状态类
        TransactionStatus status = txManager.getTransaction(info);
        String sql = "update bs_city set name=? where id=?";
        try {
            jdbcTemplate.update(sql, new Object[]{"北京", 2});
            txManager.commit(status);
        } catch (Exception e) {
            e.printStackTrace();
            txManager.rollback(status);
            return "Fail";
        }
        return "Success";
    }
}

4,声明式事务

声明式事务实现方式主要有2种:
一种为通过使用Spring的tx:advice定义事务通知与AOP相关配置实现
一种通过@Transactional实现事务管理实现

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <tx:advice id="transactionInterceptor" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" isolation="REPEATABLE_READ" />
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="aopPoint" expression="execution(* com.xzq.controller.BsCityController.*(..))"/>
        <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="aopPoint" />
    </aop:config>

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

第二种@Transactional 需要开启事务注解,注入事务管理器
其本质上spring会利用Aop对标注了@Transactional注解的方法进行事务管理。
这两种都依托与AOP

5,Spring事务失效的场景及解决办法

1,抛出检查异常导致事务不能回滚

    @RequestMapping("/testTx")
    @Transactional
    public Object testTx(String id) throws FileNotFoundException {
        BsCity city = bsCityService.getById(id);
        city.setName(String.valueOf(Math.random()));
        bsCityService.updateById(city);
        new FileInputStream("adsads");
        return "Success";
    }

原因: Spring默认只会回滚非检查异常
解决办法: 配置rollbackFor属性

2,业务方法内自己try-catch异常导致事务不能正确回滚

    @RequestMapping("/testTx")
    @Transactional
    public Object testTx(String id)  {
        BsCity city = bsCityService.getById(id);
        city.setName(String.valueOf(Math.random()));
        bsCityService.updateById(city);
        try {
            new FileInputStream("adsads");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return "Success";
    }

原因: 声明式事务的本质就是AOP代理对目标的方法进行增强,也就是在目标方法进行了try-catch,自己捉住了异常,则外层代理方法无法捕捉异常,进行正常提交
解决办法:

  • 抛出异常
  • 手动设置 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();

3,Aop切面顺序导致事务不能正常回滚

    @Pointcut("execution(* com.xzq.controller.BsCityController.*(..))")
    public void aopPoint() {
    }

    @Around("aopPoint()")
    public Object testTx(ProceedingJoinPoint pjp) {
        logger.info("》》》》》》》》》》》拦截");
        try {
         return  pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

原因: 事务切面优先级最低,如果自定义切面没有设置优先级,则也是最低,spring默认自定义的优先级最低,则事务切面在外层,自定义切面在内存,自定义切面又捉住了异常没有外抛导致事务提交。
解决办法:

  • 抛出异常
  • 手动设置 TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
  • 设置自定义切面优先级偏高于事务切面(也就是外层切面是自定义切面,内存切面是事务切面)

4,非public方法导致的事务失效

原因:spring为方法创建代理,添加事务通知,前提条件都是该方法是public的
解决办法:

  • 改为public方法
  • 设置事务属性非公共方法也可进行事务管理new AnnotationTransactionAttributeSource(false);

5,父子容器导致的事务失效

原因:子容器扫描范围过大,把未加事务配置的service扫描进来
解决办法:

  • 各扫描各的
  • 不适用父子容器,所有bean放在同一容器

传统的MVC+Spring项目会有父子容器一说
SpringBoot使用同一个容器。

6,调用本类方法导致传播行为失效

    @RequestMapping("/testProxy")
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public Object testProxy(String id) {
        BsCity city = bsCityService.getById(id);
        city.setName("666");
        bsCityService.updateById(city);
//        bsCityController.testProxy2();
//        BsCityController o =(BsCityController) AopContext.currentProxy();
//        o.testProxy2();
        return "success";
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void testProxy2() {
        BsCity city = bsCityService.getById("3");
        city.setName("777");
        bsCityService.updateById(city);
        int i = 5 / 0;
    }

如上述代码所示
在同一个Controller中定义了两个方法分别是不同的传播行为
testProxy2()方法的传播行为的含义就是创建一个新事务,暂停当前事务,也就是两个方法开启两个事务,互相独立不干扰。
那么这段代码的正确执行流程应该是
当testProxy调用testProxy2()方法时,开启新事务,出现异常,testProxy2()回滚事务,testProxy()方法提交事务。
可是却发现testProxy()也进行了回滚。
这是由于代理造成的,在本类方法中调用方法,没有经过代理,也就是说testProxy2()没有被事务切面管理。导致testProxy2()出现异常后,正常的抛出异常,没有事务管理去捕捉并进行回滚,testProxy()也抛出异常,导致testProxy()也进行了回滚。

原因: 本类方法调用不经过代理,因此无法增强
解决办法:

  • 依赖注入自己,使用代理来调用
  • 通过AOPContext拿到代理对象来调用 需要增加配置:@EnableAspectJAutoProxy(exposeProxy = true) 该配置表明,允许AopContext通过ThreadLocal拿到当前的代理对象
  • 通过CTW(编译期),LTW(加载期) 实现功能增强,编译器和加载器其实也就是在程序未运行前重组字节码织入事务代码。

7,多线程并发下@Transactional

事务的原子性仅涵盖 insert,update,delete,select…for update语句,select方法并不阻塞
比如转账业务,先查询账户金额,满足条件则进行转账。
在多线程并发下,发生指令交错,就有可能都查到满足条件的数据,进而破坏掉事务的原子性。
那么加上synchronized或者ReentranLock独占锁可以吗?
这要看你加的范围,如果仅仅加在了目标方法之上,事务切面会进行代理,commit在独占锁之外,这就又破坏了原子性
解决办法:

  • synchronized范围扩大至代理方法调用
  • 使用select…for update替换select
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值