事务的相关

事务

1.事务的概念

事务我们往往说的是数据库事务,其实还有一种事务是系统事务。
1数据库事务: 数据库事务通常指对数据库进行读或写的一个操作序列。
a数据库事务可以认为是一种容错机制,提供数据库即使在异常状态下仍能保持一致性的方法。
b当多个应用程序在并发访问数据库时,可以在这些应用程序之间提供一个隔离方法,以防止彼此的操作互相干扰。
2系统中的事务: 处理一系列业务处理的执行逻辑单元,该单元里的一系列类操作要么全部成功,要么全部失败
我们开发的的Java服务端应用说要保障事务,要保障数据一致其实是系统事务,但是系统事务的实现需要依靠数据库这个底层事务,明白了吧。

2.事务的特性

事务有四大特性,简称ACID
●原子性(Atomicity):一个事务是一系列sql的封装,这个封装不可再拆分了,这一组操作操作全部成功或者全部失败
●一致性(Consistency):数据库从一个一致性状态变到另一个一致性状态
○一系列操作后,所有的操作和更新全部提交成功,数据库只包含全部成功后的数据就是数据的一致性
○由于系统异常或数据库系统出现故障导致只有部分数据更新成功,但是这不是我们需要的最终数据,这就是数据的不一致
●隔离性(Isolation):事务互相隔离互不干扰,隔离级别可以配置的
○事务内部操作的数据对其它事务是隔离的,在一个事务执行完之前不会被其他事务影响和操作
●持久性(Durability):事务提交后数据应该被永久的保存下来,出现宕机等故障后可以恢复数据

3.事务隔离级别

ANSI SQL标准定义了4种隔离级别:分别为:READ UNCOMMITTED(未提交读)、READ COMMITTED(提交读)、REPEATABLE READ(可重复读)、SERIALIZABLE(可串行化)

READ UNCOMMITTED

在READ UNCOMMITTED级别,在事务中可以查看其他事务中还没有提交的修改。这个隔离级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会比其他级别好太多,却缺乏其他级别的很多好处,除非有非常必要的理由,在实际应用中一般很少使用。读取未提交的数据,也称为脏读(dirty read)。

READ COMMITTED

大多数数据库系统的默认隔离级别是READ COMMITTED(但MySQL不是)。READ COMMITTED满足前面提到的隔离性的简单定义:一个事务可以看到其他事务在它开始之后提交的修改,但在该事务提交之前,其所做的任何修改对其他事务都是不可见的。这个级别仍然允许不可重复读(nonrepeatable read),这意味着同一事务中两次执行相同语句,可能会看到不同的数据结果。

REPEATABLE READ

REPEATABLE READ解决了READ COMMITTED级别的不可重复读问题,保证了在同一个事务中多次读取相同行数据的结果是一样的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(phantom read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(phantom row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。REPEATABLE READ是MySQL默认的事务隔离级别。

SERIALIZABLE

SERIALIZABLE是最高的隔离级别。该级别通过强制事务按序执行,使不同事务之间不可能产生冲突,从而解决了前面说的幻读问题。简单来说,SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中很少用到这个隔离级别,除非需要严格确保数据安全且可以接受并发性能下降的结果。

4.数据库事务支持

单体应用

所有的操作都在同一个项目中,不存在远程调用,可以在方法中调用其他方法,通过编码的方式实现事务控制

分布式事务

在分布式系统中,都是在不同的服务之间进行数据操作,如果涉及到三个服务,其中两个成功,一个失败整体来说都应该失败,需要将其他服务的数据恢复,整体来说要保障数据的一致性。
恢复数据的方式一般有两种:
●事务都直接提交,最终出错的话,再次恢复数据,可能多次操作数据库
●事务都先不提交,如果都成功之后,统一提交,出现时效性问题
整体来说分布式事务就是通过将一个个的单体事务串联起来

5.Spring事务控制

操作的过程中,大家时时刻刻去带入上边的理论,Spring实现事务有两种方式:
●编程式事务:在代码中写事务的操作,一个是提交,一个是回滚,这种方式对业务侵入较高【不推荐】
●声明式事务:在spring4之前是通过xml文件配置,在spring4之后通过注解配置比较方便【推荐使用注解】
注意:事务是保障数据准确性的,只需要在写数据的时候控制好事务,读数据因为不会对数据造成影响,不需要添加事务,操作两次或以上的写【添加、修改、删除】数据添加事务

6.Spring事务实现

Spring的声明式事务通过@Transactional注解实现,该注解可以使用到类或者方法上,它有以下几个核心属性:
●value/transactionManager:这两个属性互为别名作用一样,当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器
●propagation:指定事务传播行为,默认值为 Propagation.REQUIRED
○REQUIRED:如果当前存在事务,则加入该事务,否则新建一个事务。这是最常见的传播行为,也是默认的传播行为。
○SUPPORTS:支持当前事务,如果当前不存在事务,则以非事务方式执行。
○MANDATORY:强制要求当前存在事务,如果不存在事务,则抛出异常。(mandatory:adj.强制的)
○REQUIRES_NEW:重新开启一个新的事务,如果当前存在事务,则挂起该事务。(注意:默认的事务隔离级别——可重复读中 ,开启的新事务读不到之前挂起事务的操作,而且如果操作相同的表会导致锁表,一定要谨慎使用!)
○NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则挂起该事务。
○NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。
○NESTED:如果当前存在事务,则嵌套事务中执行。嵌套事务是相对于外部事务而言的,它可以独立提交或回滚,但是嵌套事务的提交或回滚并不会对外部事务产生影响。如果外部事务不存在,那么 NESTED 与 REQUIRED 的效果是一样的。该传播行为只有在使用 JDBC 事务时才有效。
●isolation:事务隔离级别,默认值为 Isolation.DEFAULT
○Isolation.DEFAULT,使用底层数据库默认的隔离级别。
○Isolation.READ_UNCOMMITTED:
○Isolation.READ_COMMITTED:
○Isolation.REPEATABLE_READ:
○Isolation.SERIALIZABLE:
●timeout:事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务,如果要用的话基本上使用在大事务上。
●readOnly:指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true
●rollbackFor:用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
●noRollbackFor:抛出指定的异常类型,不回滚事务,也可以指定多个异常类型。

使用@Transactional正常回滚事务

@Transactional
@Override
public int updateStudent() {
    LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
    wrapper.set(Student::getUsername,"修改名字1");
    wrapper.eq(Student::getId,1817507453815693315L);
    studentMapper.update(wrapper);

    // 修改第二条数据
    wrapper.clear();
    wrapper.set(Student::getUsername,"修改名字1");
    wrapper.eq(Student::getId,1817507453815693316L);
    studentMapper.update(wrapper);
    // 报错
    int i = 1 / 0;
    return 0;
}

指定异常不回滚

/**
 * 指定异常回滚:一帮都会直接指定Exception,运行时异常/非受查异常【RunntimeException】,受查异常异常
 * 指定异常不回滚:不常用
 */
//    @Transactional(noRollbackFor = ArithmeticException.class)
@Transactional(rollbackFor = Exception.class)
@Override
public int updateStudent2() {
    LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
    wrapper.set(Student::getUsername,"忽略算术异常");
    wrapper.eq(Student::getId,1817507453815693315L);
    studentMapper.update(wrapper);

    // 修改第二条数据
    wrapper.clear();
    wrapper.set(Student::getUsername,"忽略算术异常");
    wrapper.eq(Student::getId,1817507453815693316L);
    studentMapper.update(wrapper);
    // 报错
    int i = 1 / 0;
    return 0;
}

本类方法调用

本类调用都是this.。并不会使用代理对象,比使用代理对象的方法即使有事务配置也不会生效,调用本类方法并不是嵌套事务!

/**
 * 调用本类的方法,本类的方法调用一般都是this.
 * 本类调用就没有嵌套事务,调用其他类中的方法才会有嵌套事务【调用本类都是this,调用其他类使用的就是代理对象了】
 * @return
 */
@Transactional
@Override
public int updateStudent3() {
    // 开启事务
    LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
    wrapper.set(Student::getUsername,"updateStudent3");
    wrapper.eq(Student::getId,1817507453815693315L);
    studentMapper.update(wrapper);
    // 调用method方法,this.调用的就是原方法
    method();
    // 提交/回滚
    return 0;
}

/**
 * 抽取,被复用,受不受事务控制呢?只需要考虑是否被同一个事务包裹
 * 事务若生效必须是代理的方法
 * @return
 */
@Transactional
public int method() {
    // 开启事务
    LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
    wrapper.clear();
    wrapper.set(Student::getUsername,"method");
    wrapper.eq(Student::getId,1817507453815693316L);
    studentMapper.update(wrapper);
    // 报错
    int i = 1 / 0;
    // 提交事务或者回滚事务
    return 0;
}

不同类调用

操作学生的时候,添加对应的日志,会存在嵌套事务,嵌套事务执行方式与事务传播行为有关

@Transactional
@Override
public int updateStudent4() {
    // 开启事务
    LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
    wrapper.clear();
    wrapper.set(Student::getUsername,"method");
    wrapper.eq(Student::getId,1817507453815693316L);
    studentMapper.update(wrapper);
    // 记录日志,这边就不是this调用啦,调用的是代理对象的代理方法
    operationService.insert("updateStudent4");
    // 提交或回滚事务
    return 0;
}

日志服务

@Transactional(propagation = Propagation.NOT_SUPPORTED)
@Override
public int insert(String methodName) {
    // 开启事务
    OperationLog operationLog = new OperationLog();
    operationLog.setOperationName(methodName);
    operationMapper.insert(operationLog);
    int i = 1 /0 ;
    // 提交或回滚
    return 0;
}

7.Spring事务原理

Spring的事务管理通过AOP实现,而AOP通过代理模式生成新的代理类实现的,也就是我们添加了@Transactional注解之后在生成的代理类对应的方法中会加上事务控制,
注意注意:用不用事务Spring都会通过代理模式生成原实现类的代理类,使用事务只不过是在代理类上又添加了事务控制【提交和回滚】

8.Spring事务失效

Spring中的事务失效有12种情况,可参考文章:https://blog.csdn.net/hanjiaqian/article/details/120501741

访问权限问题

众所周知,Java 的访问权限主要有四种:private、default、protected、public,它们的权限从左到右,依次变大。如果方法不是public修饰事务就会失效,在AbstractFallbackTransactionAttributeSource.computeTransactionAttribute()

@Nullable
protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
    // Don't allow non-public methods, as configured.
    if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
    }

    // The method may be on an interface, but we need attributes from the target class.
    // If the target class is null, the method will be unchanged.
    Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);

    // First try is the method in the target class.
    TransactionAttribute txAttr = findTransactionAttribute(specificMethod);
    if (txAttr != null) {
        return txAttr;
    }

    // Second try is the transaction attribute on the target class.
    txAttr = findTransactionAttribute(specificMethod.getDeclaringClass());
    if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
        return txAttr;
    }

    if (specificMethod != method) {
        // Fallback is to look at the original method.
        txAttr = findTransactionAttribute(method);
        if (txAttr != null) {
            return txAttr;
        }
        // Last fallback is the class of the original method.
        txAttr = findTransactionAttribute(method.getDeclaringClass());
        if (txAttr != null && ClassUtils.isUserLevelMethod(method)) {
            return txAttr;
        }
    }

    return null;
}

方法用fian修饰

方法内部调用

m2的事务失效

@Override
public int updateStudent() {
    LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
    wrapper.set(Student::getUsername,"修改名字1");
    wrapper.eq(Student::getId,1817507453815693315L);
    studentMapper.update(wrapper);
    m2();
    return 0;
}

@Transactional
public int m2() {
    // 修改第二条数据
    LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
    wrapper.set(Student::getUsername,"修改名字1");
    wrapper.eq(Student::getId,1817507453815693316L);
    studentMapper.update(wrapper);
    int i = 1 / 0;
    return 0;
}

未被Spring管理

使用 spring 事务的前提是:对象要被 spring 管理,需要创建 bean 实例

多线程

表不支持事务

未开启事务

上边介绍了Spring的事务传播行为,如果传播行为设置了NOT_SUPPORTED,那么事务就会失效,设置NEVER如果有事务就会抛出异常。

事务传播行为不正确

捕获异常

异常在程序中捕获了,事务识别不到发生了异常,就会失效

@Transactional
    @Override
    public int catchException() {
        Student student = new Student();
        student.setUsername("catchException张三");
        student.setPasword("11111");
        student.setStudentNo("张三-");
        student.setMajorId(111L);
        student.setClassId(111L);

        // 插入数据
        studentMapper.insert(student);
        try {
            int i = 1 / 0;
        }catch (ArithmeticException e) {
            // 捕获异常
        }

        return 0;
    }

解决方案:不要捕获,如果非要捕获,出完之后重新抛出

@Transactional
@Override
public int catchException() {
    Student student = new Student();
    student.setUsername("catchException张三重抛");
    student.setPasword("11111");
    student.setStudentNo("张三-");
    student.setMajorId(111L);
    student.setClassId(111L);

    // 插入数据
    studentMapper.insert(student);
    try {
        int i = 1 / 0;
    }catch (ArithmeticException e) {
        // 捕获异常
        log.info("抓到了算术异常======》");
        // 重新抛出
        throw new ArithmeticException();
    }
    return 0;
}

抛异常不正确

@Transactional注解默认会回滚RunntimeException,如果我们抛出的异常不属于RunntimeException就不会回滚事务,此时可以通过自定义rollBack,扩大异常范围抓取到异常回滚。

自定义异常回滚

嵌套事务

大事务

@Transactional
@Override
public int bigTran() {
    List<Student> students = studentMapper.selectList(null);
    // 可以将service导入,有事务就挂起
    List<Major> majors = majorMapper.selectList(null);
    LambdaUpdateWrapper<Student> wrapper = new LambdaUpdateWrapper<>();
    wrapper.set(Student::getUsername,"李四").like(Student::getUsername,"张三1");
    studentMapper.update(wrapper);
    // 其他的操作
    return 0;
}

有时候可能引发bbug,我们将查询的单独封装出来,有事务将其挂起。

  • 7
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在MySQL面试中,事务是一个重要的知识点,以下是一些可能会涉及的事务相关知识点: 1. 事务的四大属性:MySQL中的事务具有四大属性,即原子性、一致性、隔离性和持久性。原子性指事务是不可分割的工作单元;一致性指事务执行前后数据库的状态必须保持一致;隔离性指多个事务并发执行时,彼此之间是隔离的;持久性指事务完成后,对数据库的修改必须是永久性的。 2. 事务的隔离级别:MySQL中的事务可以设置隔离级别,包括读未提交、读已提交、可重复读和串行化四种级别。不同的隔离级别会导致不同的并发问题,需要根据具体的业务需求和数据特点选择合适的隔离级别。 3. 事务的并发问题:MySQL中的事务并发执行时,可能会出现多种并发问题,包括脏读、不可重复读、幻读等。需要通过合适的隔离级别、锁机制等手段来解决这些问题。 4. 事务的提交和回滚:MySQL中的事务可以通过提交和回滚两种方式来终止。提交会将事务中对数据库的修改永久保存,回滚会撤销事务中对数据库的修改。 5. 事务的开销和性能:MySQL中的事务会带来一定的开销和性能影响,需要根据具体的业务需求和数据特点来评估和优化事务的性能。 以上是MySQL面试中可能涉及到的事务相关的知识点,应聘者需要了解这些知识点,并能够清晰地解释其原理和应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值