Springboot @Transactional详解


)

什么是java事物?

事务必须服从ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性 (isolation)和持久性(durability)的缩写。

事务的原子性:表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。
事务的一致性:表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。
事务的隔离性:表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。
事务的持久性:表示已提交的数据在事务执行失败时,数据的状态都应该正确

并发事物导致的问题

在许多事务处理同一个数据时,如果没有采取有效的隔离机制,那么并发处理数据时,会带来一些的问题。

(1)第一类丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖。

小明去银行柜台存钱,他的账户里原来的余额为100元,现在打算存入100元。在他存钱的过程中,

银行年费扣了5元,余额只剩95元。突然他又想着这100元要用来请女朋友看电影吃饭,不打算存了。

在他撤回存钱操作后,余额依然为他存钱之前的100元。所以那5块钱到底扣了谁的?

(2)脏读:一个事务读取到另一个事务未提交的更新数据。

小明的银行卡余额里有100元。现在他打算用手机点一个外卖饮料,需要付款10元。但是这个时候,

他的女朋友看中了一件衣服95元,她正在使用小明的银行卡付款。于是小明在付款的时候,

程序后台读取到他的余额只有5块钱了,根本不够10元,所以系统拒绝了他的交易,

告诉余额不足。但是小明的女朋友最后因为密码错误,无法进行交易。小明非常郁闷,

明明银行卡里还有100元,怎么会余额不足呢?(他女朋友更郁闷。。。)

(3)幻读也叫虚读:一个事务执行两次查询,第二次结果集包含第一次中没有或某些行已经

被删除的数据,造成两次结果不一致,只是另一个事务在这两次查询中间插入或删除了数据造成的。

公司财务A在进行员工薪资核算业务,需要对小明的工资进行计算并录入系统,必须查询两次明细信息,

然后将后一次的明细信息计算总数出来。财务在第一次明细查询时,基本工资2000元,全勤奖1000元,

提成2000元,共计5000元。在进行第二次计算时,财务B突然接到通知,需要把下个月的节日福利也

在这个月的工资中发放,每人100元。于是财务B在每个人的工资明细中又加了一条节日福利100元。

而此时财务A获得第二次查询小明的工资明细后,发现工资明细变成了4条数据,总数是5100元。

两次计算结果相差100元,财务A奇怪这多出来的一条明细100元是哪里来的呢?(都怪财务B不告诉A。。。)

(4)不可重复读:一个事务两次读取同一行的数据,结果得到不同状态的结果,中间正好另一个事务更新

了该数据,两次结果相异,不可被信任。

小明在手机上购买起购价为1W元理财产品。系统首先要判断他的余额够不够购买理财产品,如果足够再获

取当前的余额,进行申请。系统第一次读取到小明的余额还剩1W元,刚好足够购买产品。但是这个时候刚好

他女朋友又看中了一个包包5000元,这次密码终于不会再错误的女朋友毫不犹豫刷了小明的银行卡买下了这

个包包。但是这个系统刚好在进行第二次确认,发现小明的余额上只有5000元,

根本不够购买。于是系统很生气,拒绝了小明的购买行为,告诉他,你真是个骗子!!!

(5)第二类丢失更新:是不可重复读的特殊情况。如果两个事物都读取同一行,然后两个都进行写操作,

并提交,第一个事物所做的改变就会丢失。

小明和女朋友一起去逛街。女朋友看中了一支口红,(对,女朋友就是用来表现买买买的)小明大方的掏出

了自己的银行卡,告诉女朋友:亲爱的,随便刷,随便买,我坐着等你。然后小明就坐在商城座椅上玩手机,

等着女朋友。这个时候,程序员的聊天群里有人推荐了一本书,小明一看,哎呀,真是本好书,还是限量发行呢,

我一定更要买到。于是小明赶紧找到购买渠道,进行付款操作。而同时,小明的女朋友也在不亦乐乎的买买买,

他们同时进行了一笔交易操作,但是这个时候银行系统出了问题,当他们都付款成功后,

却发现,银行只扣了小明的买书钱,却没有扣去女朋友此时交易的钱。哈哈哈,小明真是太开心了!

1.简单开启事物管理

@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />

2.事物注解详情

比如一个部门里面有很多成员,这两者分别保存在部门表和成员表里面,在删除某个部门的时候,假设我们默认删除对应的成员。但是在执行的时候可能会出现这种情况,我们先删除部门,再删除成员,但是部门删除成功了,删除成员的时候出异常了。这时候我们希望如果成员删除失败了,之前删除的部门也取消删除。这种场景就可以使用@Transactional事物回滚。

默认遇到throw new RuntimeException("…");会回滚
需要捕获的throw new Exception("…");不会回滚

ServiceA { 
        
      /** 
       * 事务属性配置为 PROPAGATION_REQUIRED 
       */ 
      void methodA() { 
          ServiceB.methodB(); 
      } 
   
} 
   
 ServiceB { 
        
      /** 
       * 事务属性配置为 PROPAGATION_REQUIRED 
       */ 
      void methodB() { 
      } 
        
}

2.1 指定回滚

@Transactional(rollbackFor=Exception.class) 
    public void methodName() {
       //会回滚
       throw new Exception("...");
    }

2.2 指定不回滚

@Transactional(noRollbackFor=Exception.class)
    public ItimDaoImpl getItemDaoImpl() {
        //  不会回滚
        throw new RuntimeException("注释");
    } 

2.3 如果有事务,那么加入事务,没有的话新建一个(不写的情况下)

    @Transactional(propagation=Propagation.REQUIRED) 

加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行

ServiceA.methodA的时候,
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到

自己已经运行在ServiceA.methodA
的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,

他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。

即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

2.4 容器不为这个方法开启事务

@Transactional(propagation=Propagation.NOT_SUPPORTED)

当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,

而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,

而他以非事务的状态运行完,再继续ServiceA.methodA的事务。

2.5 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务

@Transactional(propagation=Propagation.REQUIRES_NEW) 

这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,

ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,

ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。

因为ServiceB.methodB是新起一个事务,那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,

ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

2.6 必须在一个已有的事务中执行,否则抛出异常

@Transactional(propagation=Propagation.MANDATORY)

必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常

2.7 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)

 @Transactional(propagation=Propagation.NEVER) 

不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED,
而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
那么ServiceB.methodB就要抛出异常了。

2.8 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

@Transactional(propagation=Propagation.SUPPORTS) 
    
    /*
    public void methodName(){
       // 本类的修改方法 1
       update();
       // 调用其他类的修改方法
       otherBean.update();
       // 本类的修改方法 2
       update();
    }
    other失败了不会影响 本类的修改提交成功
    本类update的失败,other也失败
    */
@Transactional(propagation=Propagation.NESTED)

2.9 readOnly=true只读,不能更新,删除

@Transactional (propagation = Propagation.REQUIRED,readOnly=true) 

应用场合:
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

设置超时时间

@Transactional (propagation = Propagation.REQUIRED,timeout=30)

默认为timeout=-1

设置数据库隔离级别

@Transactional (propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)

1、Serializable:最严格的级别,事务串行执行,资源消耗最大;
2、REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。

避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。
3、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行

事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。
4、Read Uncommitted:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。
我们知道并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行,

这需要查看数据库教材的可串行化条件判断了。
这里就不阐述。
我们首先说并发中可能发生的3中不讨人喜欢的事情
1: Dirty reads–读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,

如果事务A失败回滚,会导致事务B所读取的的数据是错误的。
2: non-repeatable reads–数据不可重复读。比如事务A中两处读取数据-total-的值。

在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,

total竟然就变成200了,造成事务A数据混乱。
3: phantom reads–幻象读数据,这个和non-repeatable reads相似,也是同一个事务中

多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了

(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,

而是他的条件数据集改变。比如Select account.id where account.name=“ppgogo*”,

第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由"dd"改成"ppgogo1",

结果取出来了7个数据。 Dirty reads non-repeatable reads phantom reads
Serializable 不会 不会 不会
REPEATABLE READ 不会 不会 会
READ COMMITTED 不会 会 会
Read Uncommitted 会 会 会

3.指定事物管理器

spring Boot 使用事务非常简单,首先使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问数据库的Service方法上添加注解 @Transactional 便可。

关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。如果你添加的是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。

你可以在启动类中添加如下方法,Debug测试,就能知道自动注入的是 PlatformTransactionManager 接口的哪个实现类。

3.1 打印项目事务管理器

@EnableTransactionManagement // 启注解事务管理,等同于xml配置方式的 <tx:annotation-driven />
@SpringBootApplication
public class ProfiledemoApplication {

    @Bean
    public Object testBean(PlatformTransactionManager platformTransactionManager){
        System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
        return new Object();
    }

    public static void main(String[] args) {
        SpringApplication.run(ProfiledemoApplication.class, args);
    }

这些SpringBoot为我们自动做了,这些对我们并不透明,如果你项目做的比较大,添加的持久化依赖比较多,我们还是会选择人为的指定使用哪个事务管理器。
代码如下:

3.2 指定事务管理器

@EnableTransactionManagement
@SpringBootApplication
public class ProfiledemoApplication {

    // 其中 dataSource 框架会自动为我们注入
    @Bean
    public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public Object testBean(PlatformTransactionManager platformTransactionManager) {
        System.out.println(">>>>>>>>>>" + platformTransactionManager.getClass().getName());
        return new Object();
    }

    public static void main(String[] args) {
        SpringApplication.run(ProfiledemoApplication.class, args);
    }
}

在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。

然后在Service中,被 @Transactional 注解的方法,将支持事务。如果注解在类上,则整个类的所有方法都默认支持事务。

对于同一个工程中存在多个事务管理器要怎么处理,请看下面的实例,具体说明请看代码中的注释。

3.3 使用指定的事务管理器

@EnableTransactionManagement // 开启注解事务管理,等同于xml配置文件中的 <tx:annotation-driven />
@SpringBootApplication
public class ProfiledemoApplication implements TransactionManagementConfigurer {

    @Resource(name="txManager2")
    private PlatformTransactionManager txManager2;

    // 创建事务管理器1
    @Bean(name = "txManager1")
    public PlatformTransactionManager txManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    // 创建事务管理器2
    @Bean(name = "txManager2")
    public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
        return new JpaTransactionManager(factory);
    }

    // 实现接口 TransactionManagementConfigurer 方法,其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器
    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return txManager2;
    }

    public static void main(String[] args) {
        SpringApplication.run(ProfiledemoApplication.class, args);
    }

}

@Component
public class DevSendMessage implements SendMessage {

    // 使用value具体指定使用哪个事务管理器
    @Transactional(value="txManager1")
    @Override
    public void send() {
        System.out.println(">>>>>>>>Dev Send()<<<<<<<<");
        send2();
    }

    // 在存在多个事务管理器的情况下,如果使用value具体指定
    // 则默认使用方法 annotationDrivenTransactionManager() 返回的事务管理器
    @Transactional
    public void send2() {
        System.out.println(">>>>>>>>Dev Send2()<<<<<<<<");
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值