随笔 - 1162  文章 - 0  评论 - 16  阅读 - 59万 

一、@Transactional 注解

  @Transactional:对方法中所有的操作作为一个事务进行管理。
  如果在方法上使用,只对当前方法有效果;
  如果在类上使用,对类中所有的方法都有效果。
  @Transactional 中可以设置的属性:
progpagation——Propagation:设置事务的传播行为;
isolation——Isolation:设置事务的隔离级别;
 
timeout——int:设置事务的超时时间,事务超出指定执行时长后自动终止并回滚;
 
noRollbackFor——Class[]:哪些异常事务可以不回滚
noRollbackForClassName——String[]:(String全类名)
 
rollbackFor——Class[]:哪些异常事务需要回滚;
rollbackForClassName——String[]:(String全类名)
 
readOnly——boolean:设置事务中的一些列的操作是否为只读;
 
 

二、超时属性

  语法格式:

1
@Transactional(timeout = 3)//毫秒值

  超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。

  该属性是设置在事务强制回滚前最多可执行(等待)时间;

 

三、只读属性

  由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
  如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化 。
  只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。
 
  设置只读属性:
1
@Transactional(readOnly = true) //默认是 false

   readOnly 属性:

  该属性指定当前事务中一系列的操作是否为只读,能够加快查询,不再关心事务。

  默认值为 false,不设置为只读。
  若设置为只读(true),不管事务中有没有写的操作,MySQL 都会在请求访问数据的时候,不加锁,提高性能;
  如果有读操作,有写操作,MySQL不会加锁,这样就会产生脏读、幻读、不可重复读等问题。
  所以如果有写的操作的情况,建议一定不能设置为只读。
 
  扩展:MySQL 使用多线程+锁机制,如果设置不设置为只读,MySQL会加锁,这样CRUD什么也操作不了,可以防止 增删改造成的问题。

 

四、触发事务回滚的异常

  1、默认情况

    异常分类

      运行时异常(非检查异常):可以不用处理,默认都回滚;

      编译时异常(检查异常):要么try-catch,要么在方法上面声明 throws 默认不回滚;

    事务的回滚:捕获到 RuntimeException 或 Error 时回滚,而捕获到编译时异常不回滚。
    rollbackFor | rollbackForClassName | noRollbackFor | noRollbackForClassName:设置事务回滚的条件

  2、rollbackFor 属性

    rollbackFor 属性:指定遇到时必须进行回滚的异常类型,可以为多个;

    rollbackForClassName 属性:String 全类名

    举例:

    @Transactional(rollbackFor = {FileNotFoundException.class})
    @Transactional(rollbackForClassName = {"java.io.FileNotFoundException"})

 

  3、noRollbackFor 属性

    noRollbackFor 属性:指定遇到时不回滚的异常类型,可以为多个;

    noRollbackForClassName 属性:String 全类名

    举例:

    @Transactional(noRollbackFor = {ArithmeticException.class})
    @Transactional(noRollbackForClassName = {"java.lang.ArithmeticException"})

 

  注意:如果没有指定异常类型,抛出异常就回滚,指定异常类型,只有指定的异常发生,才回滚

 

五、事务的隔离级别

  1、数据库事务并发问题

   假设现在有两个事务:Transaction01 和 Transaction02 并发执行。

  (1)脏读

【1】Transaction01 将某条记录的 AGE 值从 20 修改为 30;
【2】Transaction02 读取了 Transaction01 更新后的值:30;
【3】Transaction01 回滚,AGE 的值恢复到了 20;
【4】Transaction02 读取的30 就是一个无效的值;(脏数据)

  

  (2)不可重复读

【1】Transaction01 读取了 AGE 的值为20;
【2】Transaction02 将 AGE 值修改为 30;
【3】Transaction01 再次读取 AGE 值为30,和第一次读取不一致;

  

  (3)幻读

【1】Transaction01 读取了 Student 表中的一部分数据;
【2】Transaction02 向 Student 表中插入了新的行;
【3】Transaction03 读取了 Student 表时,多出了一些行;(出现了幻觉)

 

  2、隔离级别

  数据库系统必须具有隔离并发运行各个事务的能力,使他们不会相互影响,避免各种并发问题。
        一个事务与其他事务隔离的程度称为隔离级别。SQL 标准中规定了多种事物隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
  (1)读未提交:READ UNCOMMITED
                        允许 Transaction01 读取 Transaction02 未提交的修改。(脏读)
        案例:
      

 

 

  (2)读已提交:READ COMMITED
                        要求 Transaction01 只能读取 Transaction02 已提交的修改。(幻读)
        案例:
      

 

 
  (3)可重复读:REPEATABLE READ
                        确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其他事务对这个字段进行更新。(不可重复读,)                
        案例:
      

 

 
  (4)串行化:SERIALIZABLE
                        确保 Transaction01 可以多次从一个表中读取到相同的行,在 Transaction01 执行期间,禁止其他事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
 

  3、各个隔离级别解决并发问题的能力表

    

 
脏读
不可重复读
幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

  4、各个数据库产品对事务隔离级别的支持程度

    

 
Oracle
MySQL
READ UNCOMMITTED
×
READ COMMITTED
√(默认)
REPEATABLE READ
×
√(默认)
SERIALIZABLE

  5、在 Spring 中指定事务隔离级别

    (1)使用注解

      用 @Transaction 注解声明式地管理事务时可以在 @Transaction 的 Isolation 属性中设置隔离级别。     

    (2)XML 方式

      在Spring 2.x事务通知中,可以在< tx:method >元素中指定隔离级别

    <tx:advice id="bookShpTXAdvice"  transaction-manager="transactionManager">
         <tx:attributes>
              <tx:method name="purchase"  propagation="REQUIRES_NEW" isolation="READ_COMMITTED"/>
         </tx:attributes>
     </tx:advice>

 

  6、详解隔离级别

    Isolation:事务的隔离级别,在并发的情况下,操作数据的一种规定
读未提交:脏读                  1
读已提交:不可重复读       2
可重复读:幻读                  4
串行化:性能低、消耗大    8
 
  读未提交、读已提交,只是对某一记录的字段的值进行修改
  可重复读是指对整个表中的记录的个数进行操作。
 
  串行化相当于单线程,其他操作(增删改查)什么也不可以执行。

 

  7、MySQL 数据设置隔离级别

1
2
3
4
5
6
7
8
9
10
11
12
查询 MySQL 的隔离级别
select @@global.tx_isolation;    //查询全局隔离级别
select @@session.tx_isolation;  //查询当前会话隔离级别
select @@tx_isolation; //默认是会话隔离级别
 
修改MySQL隔离级别
set [session | global] transaction isolation level [READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE]
 
事务操作
开启事务  start transaction
提交事务  commit
回滚事务  rollback

  

  8、并发修改同一个数据下的排队

    案例:

    

 

  9、ioc 容器中保存的是业务逻辑组件(有事务)的代理对象

 

六、事务的传播行为  

  1、传播行为

    传播行为(事务的传播+事务的行为):如果有多个事务进行嵌套运行,子事务是否要和大事务共用一个事务;

    当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
    例如:方法可能继续在现有的事务中运行,也可能开启一个新事务,并在自己的事务中运行。
    事务的传播行为可以由传播属性指定。Spring 定义了7种类传播行为

  2、Spring的7种传播行为

      

 

      事务传播属性可以在 @Transaction 注解的 propagation 属性中定义。        

  3、测试

    

 

 

 

  4、REQUIRED传播行为

    当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时,它默认会在现有的事务内运行。
            这个默认的传播行为就是 REQUIRED
            因此在 checkout() 方法的开始和终止边界内只有一个事务。这个事务只在 checkout() 方法结束的时候被提交,结果用户一本书都买不了。
    REQUIRED:同一辆车(要翻车一起翻)

事务的属性都是继承于大事务;自己的都失效

只有这一条线上任何一处出现异常,整条先全崩,回滚

将之前事务用的connection传递给这个方法使用

    

 

 

  5、REQUIRES_NEW传播行为

    表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

    REQUIRED_NEW:开一辆新车(你翻车不影响我)

不继承大事务,自己用自己的

一处事务崩,只是他崩了,跟其他没有关系,他自己回滚。

这个方法直接使用新的connection

 

    

 

 

  6、详解传播行为

  Propagation:A方法和B方法都有事务,当 A方法在调用 B 方法时,会将 A 中的事务传播给 B 方法,B方法对于事务的处理方式就是事务的传播行为。
  所以要在被调用的方法 (B方法)上面设置事务的传播行为
 
  (1)Propagation.REQUIRED:必须使用调用者的事务;
  (2)Propagation.REQUIRES_NEW:将调用者的事务挂起,不使用调用者的事务,使用新的事务进行处理;
 
   注意:默认的传播行为是 REQUIRED,默认和数据库的隔离级别是一样的

  7、REQUIRED 事务属性来源于最外层的大事务属性

    如果是 REQUIRED :事务的属性都是继承于最外层的大事务属性,而 REQUIRED_NEW 是可以调整;

  8、本类事务方法之间的调用就只是一个事务(事务失效)

    举例:

复制代码
@Service
public class BookService {

    @Autowired
    private BookDao bookDao;

    /**
     * 结账,哪个用户买了哪本书
     * @param userName
     * @param isbn
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void checkOut(String userName, String isbn) {
        bookDao.updateStock(isbn);

        int price = bookDao.getPrice(isbn);

        int i = 10 / 0;

        bookDao.updateBalance(userName, price);

    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updatePrice() {
        
    }
    
    
    @Transactional
    public void mulTx() {
        checkOut("Tom", "ISBN-001");
        updatePrice();
    }
    
}
复制代码

 

  在上面的 mulTx() 方法中,直接调用本类的事务方法,并且事务的传播行为都是 REQUIRED_NEW,但是运行 mulTx() 方法时,事务并没有正常运行。

  注意:Spring是利用AOP来实现事务的,而且在容器中保存的代理对象,我们应该调用代理对象的事务方法,直接调用本类的事务方法,并没有代理对象进行事务控制。

  上面的 mulTx() 相当于只有一个 mulTx() 一个事务方法:

    BookServiceProxy.mulTx() {
        checkOut("Tom", "ISBN-001");
        updatePrice();
    }

 

 

  正确写法:

复制代码
    @Autowired
    private BookDao bookDao; //注入的是代理对象
    
    @Transactional
    public void mulTx() {
        bookService.checkOut("Tom", "ISBN-001");
        bookService.updatePrice();
    }
复制代码

 

    通过代理对象来调用,代理对象对原来方法做了事务控制(增强)。

    本类事务方法的嵌套调用就只有一个事务。

 

 

 

 

 

 
posted on   格物致知_Tony  阅读(92)  评论(0编辑  收藏  举报
编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?
点击右上角即可分享
微信分享提示

目录导航