【Spring】事务和@Transactional注解

打印 上一主题 下一主题

主题 1972|帖子 1972|积分 5926

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
1.事务概念
Spring 事务管理是一种确保数据划一性和完备性的机制。它允许开发者在操纵数据库时将多个步骤封装在一个事务中,要么全部乐成,要么在出错时全部回滚。Spring 提供了声明式和编程式事务管理方式,通常使用 @Transactional 注解举行声明式事务管理,以简化事务处理。事务具有四个基本特性:原子性、划一性、隔离性和持久性(ACID),确保在并发环境中数据的可靠性。
事务基本特性(ACID 原则)
原子性 (Atomicity)
事务是一个不可分割的操纵单位,要么全部实行乐成,要么全部不实行。原子性确保了在事务实行过程中,若发生错误,所有已实行的操纵都会被回滚,数据库将规复到事务开始之前的状态。
划一性 (Consistency)
在事务实行前后,数据库必须保持划一性状态。也就是说,事务的实行不会破坏数据的完备性约束。完成一个事务后,数据库的状态应该是有用的,岂论是在事务开始前还是竣事后。
隔离性 (Isolation)
隔离性确保多个事务并发实行时,互不干扰。一个事务的实行不应受到其他事务的影响。隔离级别决定了事务间的可见性,通常分为以下几种:
读未提交 (Read Uncommitted)
读已提交 (Read Committed)
可重复读 (Repeatable Read)
序列化 (Serializable)
持久性 (Durability)
一旦事务被提交,其效果是永久性的,纵然体系瓦解也不会丢失。持久性保证了数据库在事务提交后的数据将被持久保存,通常通过日记或其他持久存储机制实现。
事务隔离级别
读未提交 (Read Uncommitted)
描述:事务可以读取其他事务未提交的数据。这可能导致脏读。
特点:最低的隔离级别,性能较高,但数据不划一的风险最大。
读已提交 (Read Committed)
描述:事务只能读取已经提交的数据。未提交的数据对其他事务不可见。
特点:制止了脏读,但可能发生不可重复读,即同一查询在同一事务内可能返回不同的效果。
可重复读 (Repeatable Read)
描述:在同一事务内,多次读取雷同的数据效果保持划一。这防止了不可重复读。
特点:在较高的隔离级别下,可能仍然会发生幻读,即在事务实行期间,其他事务可能插入新记录,导致查询效果变化。
串行化 (Serializable)
描述:最高的隔离级别,确保事务串行实行,完全制止脏读、不可重复读和幻读。
特点:性能较低,因为它逼迫事务列队实行,但确保数据划一性。
会出现的问题
脏读 (Dirty Read)
界说:脏读发生在一个事务读取了另一个事务尚未提交的数据。这意味着如果未提交的事务最终被回滚,那么读取的数据可能是不准确的。
示例:事务 A 更新某条记录的值,但尚未提交;此时,事务 B 读取了这个未提交的值。如果事务 A 随后回滚,那么事务 B 读取的数据将不再有用。
幻读 (Phantom Read)
界说:幻读发生在一个事务在同一实行中多次查询同样的条件,但在两次查询之间,其他事务插入或删除了满足查询条件的记录,导致查询效果不同。
示例:假设事务 A 实行了一次查询,返回了 10 条记录。然后,事务 B 插入了一条新记录,满足了事务 A 的查询条件。当事务 A 再次实行同样的查询时,返回的记录数将酿成 11 条,这就是幻读。
不可重复读 (Non-repeatable Read)
界说:不可重复读发生在一个事务在读取某条记录后,另一个事务对该记录举行了修改或删除,当第一个事务再次读取该记录时,得到的效果与第一次不同。
示例:假设事务 A 读取某条记录的值为 100。此时,事务 B 对这条记录举行了更新,将其值改为 200。如果事务 A 之后再次读取这条记录,它将得到值 200,而不是最初读取的 100。这种环境就是不可重复读。
2. @Transactional
@Transactional 注解是 Spring Framework 中用于声明式事务管理的关键注解。它允许开发者在方法上大概类上标记事务的边界,使得 Spring 在实行这些方法时自动管理数据库事务。
2.1基本用法
@Transactional 可以标注在类上大概方法上:
类上:表示该类中的所有public方法都应该在一个事务中实行。
方法上:仅表示该方法在事务中实行,类中的其他方法不受影响。
2.2 属性详解
@Transactional 提供了多个属性,帮助开发者更精细地控制事务的行为:
value:指定事务管理器的名称。如果没有指定,默认使用唯一的事务管理器。
propagation:指定事务的传播行为,默以为 Propagation.REQUIRED,表示如果存在一个事务,则参加这个事务;如果没有,则创建一个新的事务。
Propagation.REQUIRED:
说明:如果当前存在事务,则参加该事务;如果没有,则创建一个新的事务。
示例:方法 A 调用方法 B。如果方法 A 在事务中,方法 B 也在同一个事务中实行;如果方法 A 没有事务,方法 B 会创建一个新的事务
Propagation.SUPPORTS
说明:如果当前存在事务,则参加该事务;如果没有,则以非事务方式实行。
示例:方法 A 调用方法 B。如果方法 A 在事务中,方法 B 将在同一事务中实行;如果方法 A 没有事务,方法 B 将以非事务方式实行。
Propagation.MANDATORY:
说明:如果当前存在事务,则参加该事务;如果当前不存在事务,则抛出异常。
示例:方法 A 调用方法 B。如果方法 A 没有事务,调用将失败并抛出异常;如果有,则方法 B 在该事务中实行。
Propagation.REQUIRES_NEW:
说明:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。
示例:方法 A 调用方法 B。如果方法 A 有事务,方法 B 将创建一个新的事务,方法 A 的事务会被挂起。两者的事务相互独立。 A方法中调用 B方法操纵数据库, A方法抛出异常后,B方法不会举行回滚。
Propagation.NOT_SUPPORTED:
说明:以非事务的方式运行,如果当前存在事务,暂停当前的事务。
示例:方法 A 调用方法 B。如果方法 A 有事务,方法 B 将在非事务状态下实行,方法 A 的事务会被挂起。
Propagation.NEVER:
说明:以非事务的方式运行,如果当前存在事务,则抛出异常。
示例:方法 A 调用方法 B。如果方法 A 有事务,将抛出异常;如果没有事务,则正常实行。
Propagation.NESTED :
说明:如果当前存在事务,则创建一个嵌套事务;如果没有事务,则行为与 PROPAGATION_REQUIRED 雷同。
示例:方法 A 调用方法 B。如果方法 A 有事务,方法 B 将创建一个嵌套事务,可以独立提交或回滚。如果方法 A 没有事务,则方法 B 会创建一个新事务。
isolation:指定事务的隔离级别,默以为 Isolation.DEFAULT。常用的隔离级别有:
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
timeout:设置事务的超时时间,单位为秒。超过该时间,事务会被逼迫回滚。
readOnly:设置为 true 表示该事务是只读的,通常用于查询操纵,优化性能。
rollbackFor:指定哪些异常会导致事务回滚。可以是异常类或异常类的数组。
noRollbackFor:指定哪些异常不会导致事务回滚。
代码示例:
  1. public class UserService {
  2.     @Transactional(
  3.         propagation = Propagation.REQUIRES_NEW,
  4.         isolation = Isolation.READ_COMMITTED,
  5.         readOnly = true,
  6.         timeout = 5, // 事务超时时间
  7.         rollbackFor = Exception.class // 指定回滚的异常类型
  8.     )
  9.     public User getUserById(Long id) {
  10.         // 查询用户
  11.         ...
  12.     }
  13. }
复制代码

3.事务在函数间的相互调用
3.1 同一个类中函数的相互调用
场景:A类中,有两个方法,方法A1和A2;方法A1调用方法A2;方法A1被其他类调用。
示例1:方法A1和A2都添加@Transactional注解
  1. @Service
  2. public class AClass {
  3.     @Transactional(rollbackFor = Exception.class)
  4.     public void A1() {
  5.         // 业务逻辑
  6.         A2(); 
  7.     }
  8.     @Transactional
  9.     public void A2() {
  10.         // 执行数据库操作,假设抛了异常
  11.           // ...
  12.         throw new RuntimeException("函数异常!");
  13.     }
  14. }
复制代码


效果:A1被注解为 @Transactional,那么在 A1被调用时会开启一个事务。由于 A1内部调用 A2是在同一个实例内,A2会在同一个事务上下文中实行,事务会正常收效,同类调用,不涉及事务传播,相当于A2的代码加到了A1方法内。
整个调用过程都在 A1的事务控制下,包括对 A2的调用。这意味着,如果 A1中发生异常,事务将会回滚,包括 A2的操纵。
如果 A1被标记为 @Transactional,而 A2没有被标记为事务,则事务依然会收效,因为它是在同一个事务上下文中运行。
示例2:方法A1不添加@Transactional注解,A2添加@Transactional注解
  1. @Service
  2. public class AClass {
  3.     public void A1() {
  4.         // 业务逻辑
  5.         A2(); 
  6.     }
  7.     @Transactional(rollbackFor = Exception.class)
  8.     public void A2() {
  9.         // 执行数据库操作,假设抛了异常
  10.           // ...
  11.         throw new RuntimeException("函数异常!");
  12.     }
  13. }
复制代码

效果:当其他类调用 A1时,A1运行在没有事务的上下文中。随后在 A1中调用 A2,由于 A2是在 A1的上下文中直接调用的,以是它不会启动一个新的事务。
在这种环境下,A2的 @Transactional 注解不会收效,因为 Spring 的事务管理机制依靠于代理模式,同类方法调用不会调用代理对象的方法。只有当事务方法通过 Spring 的代理举行调用时,事务管理才会收效。因此,A2中的数据库操纵将会直接实行,而不受事务控制。
如果 A2中发生异常,A1也不会回滚,因为 A1本身没有事务。如果需要举行回滚,aFunction 应该被标记为 @Transactional
3.2 不同类中函数的相互调用
场景:两个类分别为A类、B类;A类有方法A、B类有方法B;A类方法A调用B类方法B;A类方法A被另一个C类调用。
示例1:方法A添加注解,方法B不添加注解
  1. @Service
  2. public class AClass {
  3.     @Autowired
  4.     private BClass bClass;
  5.  
  6.     @Transactional(rollbackFor = Exception.class)
  7.     public void A() {
  8.         // 执行数据库操作
  9.         bClass.B();
  10.     }
  11. }
  12.  
  13. @Service
  14. public class BClass {
  15.  
  16.     public void B() {
  17.         // 执行数据库操作,假设抛了异常
  18.           // ...
  19.         throw new RuntimeException("函数执行有异常!");
  20.     }
  21. }
复制代码

效果:两个函数对数据库的操纵都回滚了,因为 A被标记为 @Transactional,当它被调用时,会开启一个事务,当 A调用 B时,因为 B没有事务控制,以是它将直接在当前的事务上下文中实行。如果 B抛出异常,异常将会传播回 A,因为 A处于事务管理之下,如果 B抛出一个异常,A将会捕捉到这个异常,而且会导致整个事务回滚。
示例2:方法A不添加注解,方法B添加注解
  1. @Service
  2. public class AClass {
  3.     @Autowired
  4.     private BClass bClass;
  5.  
  6.     public void A() {
  7.         // 执行数据库操作
  8.         bClass.B();
  9.     }
  10. }
  11.  
  12. @Service
  13. public class BClass {
  14.  
  15.       @Transactional(rollbackFor = Exception.class)
  16.     public void B() {
  17.         // 执行数据库操作,假设抛了异常
  18.           // ...
  19.         throw new RuntimeException("函数执行有异常!");
  20.     }
  21. }
复制代码

效果:A、B都不会回滚。因为方法 A 运行在没有事务的上下文中,方法 B 的 @Transactional 注解不会收效,如果方法 B 抛出异常,方法 A 也会捕捉到这个异常,但不会导致任何事务回滚,因为方法 A 没有事务控制。
示例3:A、B两个函数都添加事务注解;B抛异常;A抓出异常
  1. @Service
  2. public class AClass {
  3.     @Autowired
  4.     private BClass bClass;
  5.  
  6.     @Transactional(rollbackFor = Exception.class)
  7.     public void A() {
  8.         try {
  9.             // 执行数据库操作
  10.             bClass.B();
  11.         } catch (Exception e) {
  12.             e.printStackTrace();
  13.         }
  14.     }
  15.  
  16. }
  17.  
  18. @Service
  19. public class BClass {
  20.     @Transactional(rollbackFor = Exception.class)
  21.     public void B() {
  22.         // 执行数据库操作,假设抛了异常
  23.           // ...
  24.         throw new RuntimeException("函数异常!");
  25.     }
  26. }
复制代码


效果:整个事务不会提交,且抛异常org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only,所有在方法A和方法B中的数据库操纵都会被回滚。
因为两个函数用的是同一个事务;B函数抛了异常,调了事务的rollback函数,而且事务被标记了只能rollback了;步调继续实行,A函数里面把异常给抓出来了,这个时间A函数没有抛出异常,既然没有异常那事务就需要提交,会调事务的commit函数;而之前这个事务已经被标记了只能rollback-only(因为是同一个事务),因此直接就抛异常了。
示例4:A、B两个函数都添加注解;B抛异常,A抓出异常;这里B函数@Transactional注解加了一个参数propagation = Propagation.REQUIRES_NEW,控制事务的传播行为,表明是一个新的事务
  1. @Service
  2. public class AClass {
  3.     @Autowired
  4.     private BClass bClass;
  5.  
  6.     @Transactional(rollbackFor = Exception.class)
  7.     public void A() {
  8.         try {
  9.             // 执行数据库操作
  10.             bClass.B();
  11.         } catch (Exception e) {
  12.             e.printStackTrace();
  13.         }
  14.     }
  15.  
  16. }
  17.  
  18. @Service
  19. public class BClass {
  20.     @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
  21.     public void B() {
  22.         // 执行数据库操作,假设抛了异常
  23.           // ...
  24.         throw new RuntimeException("函数异常!");
  25.     }
  26. }
复制代码

效果:B函数里面的操纵回滚了,A函数里面的操纵乐成了;因为两个函数不是同一个事务了,以是B函数抛异常只会导致B的回滚,不影响A所在事务的正常实行。
4.事务失效场景
4.1.同一类内部方法调用
场景: 如果在同一个类内部的方法中调用被 @Transactional 注解的方法,事务可能不会收效。
办理办法: 将事务方法放在不同的服务类中,大概使用 Spring 的 AOP 代理机制,使其能够通过代理举行调用。
4.2 代理对象调用
场景: 当通过非代理对象(例如直接使用 this)调用 @Transactional 方法时,事务将失效。
办理办法: 确保通过 Spring 容器管理的代理对象调用事务方法,制止使用 this。
4.3 异常范例
场景: 默认环境下,只有未检查的异常(RuntimeException)会导致事务回滚,检查异常不会导致回滚。
办理办法: 如果需要针对检查异常举行回滚,可以使用 rollbackFor 属性指定异常范例。
4.4 事务传播行为
场景: 选择不妥的事务传播行为可能导致事务失效,例如使用 PROPAGATION_NOT_SUPPORTED 会以非事务方式实行。
办理办法: 根据业务需求选择合适的传播行为,确保方法调用之间的事务关系。
4.5.嵌套事务
场景: 嵌套事务可能不会按照预期工作,特别是在使用 PROPAGATION_NESTED 时。
办理办法: 了解嵌套事务的行为,并确保使用合适的事务传播策略。
4.6 多线程环境
场景: 在多线程环境中,事务可能无法正常传播。
办理办法: 确保在同一线程中处理事务,大概使用合适的方式管理跨线程事务。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表