12.Spring声明式事务

本文最后更新于2023.11.30-17:53,某些文章具有时效性,若有错误或已失效,请在下方留言或联系涛哥

1.什么是事务?

一组数据库的增删改查,创建(Create)、更新(Update)、读取(Retrieve)和删除(Delete)操作,要么同时成功,要么同时失败,作为一个整体来考虑,我们把他叫做事务。

2.事务的4大特性 ACID

A:原子性:

一组数据库操作(增删改) 同时成功 或者同时失败,这组操作是作为一个 整体,不可分割。

C:一致性:

在一个事务中 ,数据库操作(查询) 应该保持查询结果一致

I:隔离性

多个事务之间,相互隔离互不干扰。

D:持久性

数据一旦保存就是持久的(提交之后)

3.事务的隔离规则

mysql默认的事务处理级别是 repeatable read ,而Oracle和SQL Server是 read committed

最低的隔离级别:Read-uncommited(读未提交)

db补充:

脏读

概念:当一个事务 正在访问数据,并且对数据进行了修改,而这种修改还没有被提交到数据库,这是另外一个事务也访问了这个数据,然后使用了这个数据.

1老马 原工资是 1000块,财务人员将老马的工资 改为了 8000 (开启了事务,但是并未提交)

2老马 查询工资 发现自己要发的工资是8000快。很开心。

3 而财务发现 这个操作有问题 要进行回滚,工资其实还是1000.

4.老马读到了一条没有提交的数据8000 所谓的脏读。

处理方法:设置数据库的隔离级别,只允许访问 提交了的数据 

设置 隔离级别:READ-COMMITTED

不可重复读

概念:指在同一个事务A内,多次读取了同一个数据,A还没有结束,事务B也访问了同一个数据,那么在事务A读取的两次数据中,由于事务B对 数据进行了修改,导致一个事务A读取的 数据是不一样的,那么这种在一个事务内读到两次不同数据的,叫做 不可重复读

1.事务A 老马读取了自己的工资 8000块,操作并没有完成

2.事务B 把老马的工资 改成了1000快,并进行了提交(committed)

3.事务A 老马挑了一个8000块的电脑,付款时,再次查询了工资 结果只有1000快

进行了一次不可重复的读

解决办法; 原理(行锁) 只有事务A 完全提交了数据,事务B 才能去访问以及读取 老马的工资

设置隔离级别:repeatable read

幻读

概念:幻读当事务 不是独立执行时 发生的一种现象: 例如 第一个事务 对一个表中的 数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也在修改这个表中的数据,这种修改时 向表中插入一条数据。,那么发生第一个事务的用户就会发现,表中还有没有修改的数据行,称之为幻读。

1.目标工资为1000的员工有 10个人

2事务1 :查询所有工资为1000的员工, 得到的结果为10

3事务2 插入了一条数据 新员工 工资也为1000

4 事务1 再去读取工资为1000的员工 结果是11条

解决办法:(锁表) 在事务的操作执行完之前,其他事务都不可以添加新数据

设置 隔离级别;serializable

  • read-uncommitted:会有 脏读 不可重复度 幻读的问题
  • read-committed :解决了 脏读 但仍然有 不可重复读 和幻读
  • repeatable-read :解决了 脏读 不可重复读的问题 幻读没有解决
  • serializable :解决了 一切,但是性能底下。

传播行为

  • REQUIRED:表示当前这个方法 的执行必须要在 事务中,如果已经处于事务环境,则直接使用当前事务,如果没有处于 则开启一个新的事务。

method1() method2()

method1 调用了 method2 那么在1已经开启事务的情况下 那么method2 会直接和1 公用一个事务。

  • SUPPORTS:表示当前方法如果 已经开启事务 那么就使用 同一个事务,如果没开启事务,那么就不开启新事务。
  • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • MANDATORY:表示当前方法的执行,必须要处于事物环境,否则抛出异常。
  • REQUIRES_NEW:表示当前方的执行 必须开启一个新的事务,如果已经存在事务,那么原先的事务暂停。
  • NEVER:该方法的执行 必须不在事务里。
  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

事务超时

timeout:设置 一个事务允许执行的最长时间,超时候 会整个回滚。

结论

在实战中事务的使用方式

如果当前业务方法是一组 增、改、删 可以这样设置事务 @Transactional

如果当前业务方法是一组 查询 可以这样设置事务 @Transactionl(readOnly=true)

如果当前业务方法是单个 查询 可以这样设置事务 @Transactionl(propagation=propagation.SUPPORTS ,readOnly=true)

使用注解完成声明式事务

1.配置 事务管理器 开启事务注解:

 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
​
 <!--启用注解  -->
   <tx:annotation-driven transaction-manager="transactionManager"  ></tx:annotation-driven>

2.可以在对应的接口 类 方法上面加上注解,可以配置隔离级别,传播方式,超时时间,以及哪些异常回滚,哪些异常不回滚。

package com.rimi.service.impl;
​
import com.rimi.bean.User;
import com.rimi.dao.UserMapper;
import com.rimi.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
​
@Service
@Transactional
public class UserServiceImpl implements UserService {
​
    @Autowired
    private UserMapper userMapper;
​
​
​
    public User selectUser(int userid) {
      return userMapper.selectUserById(userid);
    }
​
    @Transactional(propagation = Propagation.SUPPORTS)
    public boolean transformMoney(int payId, int getId, int money) {
        int result1 = 0;
        int result2 = 0;
        result1 = userMapper.updateUser(payId,0-money);
        System.out.println(5/0);
        result2 = userMapper.updateUser(getId,money);
        return result1+result2==2;
    }
}

 基于配置的

<bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- 通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 传播行为 -->
            <tx:method name="save*" propagation="REQUIRED"  />
            <tx:method name="insert*" propagation="REQUIRED" />
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="create*" propagation="REQUIRED" />
            <tx:method name="delete*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="select*" propagation="SUPPORTS" read-only="true" />
            <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>
    <!-- 切面 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice"
            pointcut="execution(* com.gxzh.dsmng.*.service.*.*(..))" /> </aop:config>
阅读剩余
THE END