当前位置: 首页 > article >正文

Spring AOP(定义、使用场景、用法、3种事务、事务失效场景及解决办法、面试题)

目录

1. AOP定义?

2.常见的AOP使用场景:

3.Spring AOP用法

3.1 Spring AOP中的几个核心概念

3.1.1 切面、切点、通知、连接点

3.1.2 切点表达式AspectJ

3.2 使用 Spring AOP 的步骤总结

3.2.1 添加依赖:

3.2.2 定义切面和切点(切点和通知分开写)

3.2.3 定义通知

3.2.4 定义切面切点简化写法

3.2.5 测试

3.2.6 执行结果 ​

4.Spring支持哪些事务?事务的实现方式和原理?

4.1编程式事务

4.1.1 定义

4.1.2 优缺点

4.2 声明式事务

4.2.1 定义

4.2.2 优缺点

4.2.3 编程式事务与声明式事务区别

4.2.4 声明式事务失效场景★

1. 异常捕获提前处理

2. 抛出受检异常/检查异常

3.Spring只能代理public方法,非public方法导致声明式事务失效没有回滚

4.数据库本身不支持事务

5.多线程调用

6.自己调用自己的内部方法,导致类没被spring代理,从而失效。

4.3 注解式事务

5.面试题


1. AOP定义?

AOP称为面向切面编程,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。

2.常见的AOP使用场景:

  1. 日志记录(Logging):在方法调用前后记录日志信息,用于跟踪方法执行情况、性能监控或调试。
  2. 权限检查(Security/Authorization):在方法执行前验证用户是否有权限执行该操作,比如角色检查或资源访问控制。
  3. 事务管理(Transaction Management):自动管理数据库事务的开启、提交或回滚,保证数据的一致性。
  4. 异常处理(Exception Handling):集中处理特定类型的异常,比如记录异常信息或执行特定的恢复操作。
  5. 性能监控(Performance Monitoring):监控方法执行时间,帮助识别和优化性能瓶颈。
  6. 缓存(Caching):自动缓存方法的返回结果,减少不必要的数据库查询或其他耗时操作。
  7. 参数校验和转换(Parameter Validation and Conversion):在方法调用前对参数进行校验或转换,确保符合业务逻辑要求。
  8. API调用统计(API Call Tracking):记录API的调用次数、频率等,用于分析和优化。
  9. SLF4J、Logback、Log4j等日志框架集成:通过AOP可以在不修改业务代码的情况下,灵活地切换或增强日志框架的功能。
  10. 自定义注解的处理:使用AOP拦截带有特定自定义注解的方法,实现特定逻辑,如标记某个方法需要审计、限流等

3.Spring AOP用法

3.1 Spring AOP中的几个核心概念

3.1.1 切面、切点、通知、连接点

切面:  切面=切点+通知

切点:切点是用来定义哪些连接点会被切面所拦截的表达式。通过切点,可以指定特定的类和方法,确保切面只在感兴趣的地方应用。

通知:

通知是切面在特定连接点执行的操作。主要有几种类型的通知:

  • 前置通知(@Before): 在目标方法执行之前执行。
  • 后置通知(@After): 在目标方法执行之后执行(无论成功与否)。
  • 返回通知(@AfterReturning): 在目标方法成功执行后执行。
  • 异常通知(@AfterThrowing): 在目标方法抛出异常时执行。
  • 环绕通知(@Around): 包裹目标方法的调用,可以在目标方法执行前后自定义逻辑。

连接点:这是一个规定的逻辑概念,并没有对应的注解和配置,连接点是应用程序执行中的一个点,可以是方法调用、对象创建等。Spring AOP 主要关注方法执行的连接点。

3.1.2 切点表达式AspectJ

AspectJ 表达式语法:

@Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")

AspectJ 语法(Spring AOP 切点的匹配语法):

切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:

execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)

AspectJ ⽀持三种通配符

* :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)

… :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。

+ :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有⼦类包括本身

修饰符,一般省略

  • public 公共方法
  • *任意


返回值,不能省略

void 返回没有值
String 返回值字符串
* 任意

包,通常不省略,但可以省略

com.gyf.crm 固定包
com.gyf.crm.*.service crm 包下面子包任意(例如:com.gyf.crm.staff.service)
com.gyf.crm… crm 包下面的所有子包(含自己)
com.gyf.crm.*service… crm 包下面任意子包,固定目录 service,service 目录任意包

类,通常不省略,但可以省略

UserServiceImpl 指定类

*Impl 以 Impl 结尾

User* 以 User 开头

* 任意

方法名,不能省略

addUser 固定方法

add* 以 add 开头

*DO 以 DO 结尾

* 任意

参数

() 无参

(int) 一个整形

(int,int)两个整型

(…) 参数任意

throws可省略,一般不写

表达式示例

execution(* com.cad.demo.User.*(…)) :匹配 User 类⾥的所有⽅法
execution(* com.cad.demo.User+.*(…)) :匹配该类的⼦类包括该类的所有⽅法
execution(* com.cad..(…)) :匹配 com.cad 包下的所有类的所有⽅法
execution(* com.cad….(…)) :匹配 com.cad 包下、⼦孙包下所有类的所有⽅法
execution(* addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个参数类型是 int

AOP举例如下:

3.2 使用 Spring AOP 的步骤总结

3.2.1 添加依赖:

pom.xml 中添加 Spring AOP 的相关依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

3.2.2 定义切面和切点(切点和通知分开写)

  • 使用 @Aspect 注解标记切面类。
  • 定义切点,使用 @Before@After@Around 等注解实现通知。
@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义一个切点(设置拦截规则)
    @Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")
    public void pointcut() {
    }
}

3.2.3 定义通知

  • 前置通知 @Before:通知方法会在目标方法调用之前执行
  • 后置通知 @After:通知方法会在目标方法返回或者抛出异常后调用
  • 返回之后通知 @AfterReturning:通知方法会在目标方法返回后调用
  • 抛异常后通知:@AfterThrowing:通知方法会在目标方法爬出异常之后调用
  • 环绕通知:@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为

实现通知方法也就是在什么时机执行什么方法

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
    // 定义一个切点(设置拦截规则)
    @Pointcut("execution(* com.example.springaop.controller.UserController.*(..))")
    public void pointcut() {
    }

    // 定义 pointcut 切点的前置通知
    @Before("pointcut()")
    public void doBefore() {
        System.out.println("执行前置通知");
    }

    // 后置通知
    @After("pointcut()")
    public void doAfter() {
        System.out.println("执行后置通知");
    }

    // 返回之后通知
    @AfterReturning("pointcut()")
    public void doAfterReturning() {
        System.out.println("执行返回之后通知");
    }

    // 抛出异常之后通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing() {
        System.out.println("执行抛出异常之后通知");
    }
}

3.2.4 定义切面切点简化写法

简化写法就是切点无参构造方法不写,简化后如下:

@Aspect // 当前类是一个切面
@Component
public class UserAspect {
 
    // 定义 pointcut(设置拦截规则)和 切点的前置通知
    @Before("execution(* com.example.springaop.controller.UserController.*(..))")
    public void doBefore() {
        System.out.println("执行前置通知");
    }

    // 定义 pointcut(设置拦截规则)和 后置通知
    @After("execution(* com.example.springaop.controller.UserController.*(..))")
    public void doAfter() {
        System.out.println("执行后置通知");
    }

    //定义 pointcut(设置拦截规则)和  返回之后通知
    @AfterReturning("execution(* com.example.springaop.controller.UserController.*(..))")
    public void doAfterReturning() {
        System.out.println("执行返回之后通知");
    }

    // 定义 pointcut(设置拦截规则)和 抛出异常之后通知
    @AfterThrowing("execution(* com.example.springaop.controller.UserController.*(..))")
    public void doAfterThrowing() {
        System.out.println("执行抛出异常之后通知");
    }
}

3.2.5 测试

@RestController
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/sayhi")
    public String sayHi() {
        System.out.println("sayhi 方法被执行");
        int num = 10/0;
        return "你好,java";
    }

    @RequestMapping("/sayhi2")
    public String sayHi2() {
        System.out.println("sayhi2 方法被执行");
        return "你好,java2";
    }
}

3.2.6 执行结果
 

4.Spring支持哪些事务?事务的实现方式和原理?

4.1编程式事务

4.1.1 定义

在代码中显式地手动编写事务管理相关代码,如开启事务、提交事务、回滚事务等。较为繁琐,不推荐。(一般是基于底层的API,如TransactionDefinition 和 TransactionTemplate 等核心接口,使得开发者完全通过编程的方式来进行事务管理。现在项目比较少使用。)\

4.1.2 优缺点

优点:提供了更精准的控制,相比于声明式事务管理粒度更小。
缺点:开发者需要在代码中手动实现事务的开启、提交、回滚等操作,较为繁琐。

4.2 声明式事务

4.2.1 定义

简答:

使用 AOP技术,在代码中通过配置进行声明,从而实现对事务管理的控制。

详答:

声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。

开发者只需要添加配置即可, 具体事务的实现由第三方框架例如Spring AOP实现,避免我们直接进行事务操作!

使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。

4.2.2 优缺点

优点:
不再需要依赖底层API来硬编码,对业务代码没有侵入性。
适用于事务边界清晰、事务属性统一的场合,譬如最经典的CRUD业务。

缺点:
存在粒度问题。其最小粒度要作用在方法上。
存在一些事务失效的情况。

4.2.3 编程式事务与声明式事务区别

- 编程式事务需要手动编写代码来管理事务
- 而声明式事务可以通过配置文件或注解来控制事务。

4.2.4 声明式事务失效场景★

1. 异常捕获提前处理

解决方案:在catch中 throw new RuntimeException(e)抛出异常。

举例说明:

在Spring框架中,声明式事务管理是通过Spring AOP实现的。这意味着Spring会在方法执行前后添加事务控制的逻辑。通常情况下,如果一个方法在事务中执行并且抛出了异常,Spring的事务管理器会捕捉到这个异常并进行回滚。但是,如果方法内部通过try-catch块捕获了异常并且没有将其抛出,那么Spring的事务管理器就无法知道需要回滚事务,因为从方法的返回来看,一切都是正常的。

@Transactional
public void someServiceMethod() {
    // 业务逻辑代码
    try {
        // 一些数据库操作,可能会抛出异常
        if (someCondition) {
            throw new CustomException("Something went wrong");
        }
    } catch (CustomException e) {
        // 异常被捕获并处理,没有重新抛出
        // 处理异常的逻辑
    }
    // 其他业务逻辑代码
}

在这个例子中,someServiceMethod方法内部有一个try-catch块,它会捕获CustomException异常并处理它,没有将异常抛出方法外。由于Spring的事务管理是基于异常传播的,如果没有异常被抛出,Spring就认为业务执行成功,不会触发事务回滚。

解决方案

为了确保事务在异常发生时能够回滚,我们需要在catch块中将异常重新抛出,这样Spring的事务管理器就能捕获到异常并执行回滚操作。我们可以将异常包装成运行时异常(RuntimeException)抛出,因为运行时异常是unchecked exception,不需要声明抛出:

@Transactional
public void someServiceMethod() {
    // 业务逻辑代码
    try {
        // 一些数据库操作,可能会抛出异常
        if (someCondition) {
            throw new CustomException("Something went wrong");
        }
    } catch (CustomException e) {
        // 异常被捕获并处理,但重新抛出RuntimeException
        throw new RuntimeException("Error handling business logic", e);
    }
    // 其他业务逻辑代码
}

在这个修改后的例子中,即使CustomException在方法内部被捕获,我们通过抛出一个新的RuntimeException确保了异常能够传播到Spring的事务管理器,从而触发事务回滚。

2. 抛出受检异常/检查异常

解决方案:在注解上额外配置rollbackFor属性,@Transactional(rollbackFor=Exception.class)

举例说明:

在Spring框架中,声明式事务管理是通过@Transactional注解来实现的。默认情况下,Spring的声明式事务仅在遇到运行时异常(RuntimeException)或错误(Error)时才会回滚事务。对于受检异常(checked exceptions),也就是那些需要在方法签名中用throws关键字声明的异常,Spring不会自动回滚事务,除非你在@Transactional注解中指定。

假设我们有一个服务方法,它在事务中执行数据库操作,并且可能会抛出一个受检异常:

@Transactional
public void processData() throws CustomCheckedException {
    // 业务逻辑代码
    if (someCondition) {
        throw new CustomCheckedException("Something went wrong");
    }
    // 更多业务逻辑代码
}

在这个例子中,CustomCheckedException是一个受检异常,如果processData方法内部抛出了这个异常,Spring的声明式事务默认不会回滚事务,因为CustomCheckedException("Something went wrong")不是非受检异常。

解决方案

为了确保在抛出受检异常时事务能够回滚,我们可以在@Transactional注解中使用rollbackFor属性来指定哪些异常会导致事务回滚。例如,我们可以指定当抛出CustomCheckedException时事务应该回滚:

@Transactional(rollbackFor = CustomCheckedException.class)
public void processData() throws CustomCheckedException {
    // 业务逻辑代码
    if (someCondition) {
        throw new CustomCheckedException("Something went wrong");
    }
    // 更多业务逻辑代码
}

在这个修改后的例子中,当processData方法内部抛出CustomCheckedException时,Spring的声明式事务会回滚事务。

通用解决方案

如果你想要让所有异常都导致事务回滚,无论是受检异常还是非受检异常,你可以使用rollbackFor属性来指定Exception.class作为参数:

@Transactional(rollbackFor = Exception.class)
public void processData() throws CustomCheckedException {
    // 业务逻辑代码
    if (someCondition) {
        throw new CustomCheckedException("Something went wrong");
    }
    // 更多业务逻辑代码
}

使用rollbackFor = Exception.class意味着任何类型的Exception(包括受检和非受检异常)都会导致事务回滚。

但是需要注意:

  • 使用rollbackFor = Exception.class可能会导致隐藏一些不应该回滚的异常,因为有些异常可能是预期内的业务逻辑异常,不应该触发事务回滚。

3.Spring只能代理public方法,非public方法导致声明式事务失效没有回滚

解决方案:改为public

举例说明:

Spring使用动态代理来实现AOP功能,包括声明式事务管理。对于基于接口的代理,Spring使用JDK的Proxy类来创建代理对象,这要求目标对象必须实现至少一个接口。对于没有接口的类,Spring使用CGLIB库来创建代理对象。无论是JDK代理还是CGLIB代理,它们都只能代理public方法,因为非public方法在代理对象中无法被正确地拦截和转发。

假设我们有一个类BusinessService,其中包含一个非public的方法,我们尝试在这个方法上使用@Transactional注解:

public class BusinessService {
    
    private void someMethod() {
        // 业务逻辑代码
    }
}

在这个例子中,someMethod是一个非public方法,即使你在这个方法上添加了@Transactional注解,Spring也不会为其创建代理,因为Spring只能为public方法创建代理。这意味着@Transactional注解在这个非public方法上不会生效。'

为了确保@Transactional注解能够生效,你需要将方法改为public

public class BusinessService {
    
    @Transactional
    public void someMethod() {
        // 业务逻辑代码
    }
}
4.数据库本身不支持事务

解决办法:手动编写回滚操作或者迁移到支持事务的数据库中。

在关系型数据库中,并非所有的数据库都默认支持事务。例如,某些数据库(如MySQL)的某些存储引擎(如MyISAM)默认不支持事务,而其他存储引擎(如InnoDB)则支持。

5.多线程调用

解决方案:可以尝试以下方法:

1、调整事务的隔离级别到更高级别。

2、使用乐观锁/悲观锁

3、使用分布式事务管理器

举例说明:

Spring的声明式事务是基于AOP代理的,而AOP代理通常只对单线程中的直接方法调用有效。当多个线程调用同一个代理对象的方法时,Spring的事务管理可能无法正确识别和管理这些线程中的事务。以下是一些解决方案的详细说明:

解决方案1. 调整事务的隔离级别到更高级别

        事务隔离级别定义了一个事务可能受其他并发事务影响的程度。不同的数据库和JDBC驱动支持不同的事务隔离级别,包括READ_UNCOMMITTED(读未提交)、READ_COMMITTED(读已提交)、REPEATABLE_READ(可重复读)和SERIALIZABLE(串行化)。提高事务隔离级别可以减少并发事务间的冲突,但可能会降低系统的并发性能。

@Transactional(isolation = Isolation.SERIALIZABLE)
public void transfer(Account from, Account to, BigDecimal amount) {
    from.setBalance(from.getBalance().subtract(amount));
    to.setBalance(to.getBalance().add(amount));
}

在这个例子中,我们将事务的隔离级别设置为SERIALIZABLE,这是最高的隔离级别,可以防止脏读、不可重复读和幻读,但可能会对性能有较大影响。

解决方案2. 使用乐观锁/悲观锁

乐观锁和悲观锁是两种并发控制机制,用于处理并发更新数据时可能出现的问题。

  • 乐观锁:假设冲突发生的概率很小,只在数据提交更新时检查是否有其他事务修改了数据。通常通过在数据表中添加一个版本号或时间戳字段来实现。
@Transactional
public void updateWithOptimisticLocking(Entity entity) {
    // 获取实体数据
    Entity found = entityManager.find(Entity.class, entity.getId());
    if (found.getVersion() != entity.getVersion()) {
        throw new ConcurrentModificationException("Data has been updated by another transaction");
    }
    // 更新实体数据
    found.setVersion(found.getVersion() + 1);
}
  • 悲观锁:假设冲突发生的概率很高,会在事务开始时锁定数据,直到事务结束。这通常通过数据库的锁机制实现,如行锁或表锁。
@Transactional
public void updateWithPessimisticLocking(Entity entity) {
    // 使用SELECT FOR UPDATE语句锁定数据
    Entity found = entityManager.createQuery("SELECT e FROM Entity e WHERE e.id = :id", Entity.class)
                                  .setParameter("id", entity.getId())
                                  .setLockMode(LockModeType.PESSIMISTIC_WRITE)
                                  .getSingleResult();
    // 更新实体数据
}

在这个例子中:

  1. @Transactional注解标记了这个方法是在一个事务中执行的。
  2. entityManager.createQuery创建了一个JPA查询,用于从数据库中检索Entity实体。
  3. "SELECT e FROM Entity e WHERE e.id = :id"是JPA查询的字符串,它指定了从Entity实体类中选择与给定id匹配的实体。
  4. .setParameter("id", entity.getId())设置了查询参数id的值。
  5. .setLockMode(LockModeType.PESSIMISTIC_WRITE)设置了锁模式为悲观写锁,这会触发数据库层面的SELECT FOR UPDATE操作。
  6. .getSingleResult()执行查询,并返回查询结果。

当这个查询执行时,数据库会锁定Entity表中对应id的行,直到当前事务结束。这意味着,如果另一个事务尝试更新或删除这些被锁定的行,它将会被阻塞,直到第一个事务提交或回滚,释放了锁。

解决方案3. 使用分布式事务管理器

在微服务架构或需要跨多个数据库进行事务管理的场景中,可能需要使用分布式事务管理器。Spring提供了对 JTA(Java Transaction API)的支持,可以通过JTA实现分布式事务。

@TransactionManagement(TransactionManagementType.JTA)
public class SomeService {

    @Transactional
    public void performSomeTransaction() {
        // 执行跨多个数据库的操作
    }
}

在这个例子中,我们使用@TransactionManagement注解指定了事务管理类型为JTA,这样就可以在多个数据库之间管理事务。

6.自己调用自己的内部方法,导致类没被spring代理,从而失效。

解决方案:可以尝试以下方法:
1、检查事务传播行为。例如,可以使用Propagation.REQUIRED传播行为,使得内部方法加入到外部方法的事务中,保证事务的一致性。
2、考虑异步调用:如果内部方法可以异步执行,并且事务一致性的要求不高,可以将内部方法改为异步调用,让其在独立的线程中执行。通过异步调用,可以避免事务嵌套导致的死锁或其他并发问题。
3、使用编程式事务控制。

举例说明:

在Spring中,声明式事务通常是基于代理的,这意味着Spring会为被@Transactional注解标记的方法创建一个代理对象,并在代理对象的方法调用链中添加事务管理逻辑。如果一个类没有被Spring代理,那么@Transactional注解将不会生效。这种情况可能发生在类自己调用自己的内部方法时,因为这些调用不会经过Spring代理,因此事务管理逻辑不会被触发。

例如下面这个:自己调用自己的内部方法,导致类没被spring代理

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class AccountService {

    private AccountRepository accountRepository;

    public AccountService(AccountRepository accountRepository) {
        this.accountRepository = accountRepository;
    }

    // 外部方法,被Spring代理,拥有声明式事务管理
    @Transactional
    public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        // 获取账户信息
        Account fromAccount = accountRepository.findById(fromAccountId);
        Account toAccount = accountRepository.findById(toAccountId);

        // 内部方法,没有被Spring代理,没有声明式事务管理
        deductAmount(fromAccount, amount);
        addAmount(toAccount, amount);
    }

    // 内部方法,非public,自己调用自己的方法
    private void deductAmount(Account account, BigDecimal amount) {
        account.setBalance(account.getBalance().subtract(amount));
        accountRepository.save(account); // 这里的保存操作不会在@Transactional的保护下执行
    }

    private void addAmount(Account account, BigDecimal amount) {
        account.setBalance(account.getBalance().add(amount));
        accountRepository.save(account); // 这里的保存操作也不会在@Transactional的保护下执行
    }
}

在这个例子中,transferMoney方法被@Transactional注解标记,意味着Spring会为这个方法创建一个代理,并在这个方法执行时管理事务。然而,deductAmountaddAmount是私有方法,它们被transferMoney内部调用。由于这些私有方法不是通过代理调用的(因为它们是同一个类内部的直接调用),Spring事务管理器不会为这些私有方法的调用添加事务管理逻辑。

这意味着如果在deductAmountaddAmount中的数据库操作失败,事务不会回滚,因为这些操作没有被Spring的事务管理器所管理。这就导致了事务失效的问题。

如果我把 deductAmount 和 addAmount方法都由private改为public类型,Spring就可以为它们创建代理了吗?

答案:不会!Spring只能代理 public类型的方法,但不是所有的puclic方法都会被代理!

因为当一个方法在同一类内部被调用时,通常使用 this 关键字,尽管在代码中不显式地写出 this,默认情况下就是通过当前对象调用的。这种方式会导致方法调用不会被 Spring 的代理机制拦截。 

以下是一些解决方案的详细说明:

解决方案1:检查事务传播行为

Spring支持不同的事务传播行为,其中Propagation.REQUIRED是最常用的一种。它表示如果当前存在事务,那么就加入该事务;如果当前没有事务,那么就创建一个新的事务。使用Propagation.REQUIRED可以确保即使内部方法被调用,它们也能加入到外部方法的事务中。

public class Service {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void outerMethod() {
        // 外部方法的逻辑
       innerMethod();
    }
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void innerMethod() {
        // 内部方法的逻辑
    }
}

在这个例子中,即使innerMethod是被outerMethod内部调用,由于两者都设置了Propagation.REQUIREDinnerMethod会加入到outerMethod的事务中,从而保证了事务的一致性。

解决方案2:考虑异步调用

如果内部方法可以异步执行,并且对事务一致性的要求不高,可以将内部方法改为异步调用。这样,内部方法会在独立的线程中执行,避免了事务嵌套的问题。

public class Service {
    
    @Transactional
    public void outerMethod() {
        // 外部方法的逻辑
       innerMethodAsync();
    }
    
    public void innerMethodAsync() {
        // 内部方法的异步逻辑
        new Thread(() -> {
            // 执行内部方法的逻辑
        }).start();
    }
}

在这个例子中,innerMethodAsync在一个新的线程中异步执行,因此不会受到外部方法outerMethod的事务影响。

解决方案3:使用编程式事务控制

编程式事务控制意味着你将手动管理事务的边界,而不是依赖于声明式事务。这可以通过使用PlatformTransactionManager来实现。

public class Service {
    
    private PlatformTransactionManager transactionManager;
    
    public Service(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
    
    public void outerMethod() {
        TransactionDefinition def = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            // 外部方法的逻辑
            innerMethod();
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
    
    public void innerMethod() {
        // 内部方法的逻辑
    }
}

在这个例子中,我们使用PlatformTransactionManager手动开始和提交事务。这种方法给了你更多的控制权,但同时也增加了代码的复杂性。

注意事项

  • 使用Propagation.REQUIRED可以确保内部方法加入到外部方法的事务中,但可能会导致事务嵌套,需要谨慎处理。
  • 异步调用可以避免事务嵌套问题,但可能会使得事务管理更加复杂,特别是在需要保证数据一致性的情况下。
  • 编程式事务控制提供了最大的灵活性,但也需要更多的代码和对事务管理的深入理解。

     

4.3 注解式事务

基于声明式事务管理,使用注解(@Transactional)的方式进行事务的管理。

5.面试题


http://www.kler.cn/a/375101.html

相关文章:

  • 38.第二阶段x86游戏实战2-HOOK窗口消息机制(解决多开窗口句柄问题)
  • 从0开始的STM32学习之旅:使用中断完成等待型任务(理论部分)
  • Unity3D Shader实现法线贴图功能详解
  • SWAT-MODFLOW地表水与地下水耦合实践技术
  • 「Mac畅玩鸿蒙与硬件12」鸿蒙UI组件篇2 - Image组件的使用
  • 创建一个基于Java的图书馆管理系统
  • Spring beanFactoryPostProcessor
  • Redis 线程控制 问题
  • 在linux中是如何运行一个应用程序的?
  • (七)JavaWeb后端开发1——Maven
  • 大语言模型驱动的跨域属性级情感分析——论文阅读笔记
  • 创造tips的秘籍——PHP回调后门
  • Redis 实战 问题
  • 【Sublime Text】格式化Json和XML
  • 线代的几何意义(一)——向量,坐标,矩阵
  • thinkphp和vue基于Workerman搭建Websocket服务实现用户实时聊天,完整前后端源码demo及数据表sql
  • Docker部署jenkins容器时,允许jenkins容器内部控制宿主机上的docker
  • 正向解析,反向解析
  • CSS3新增长度单位(二)
  • 从比亚迪超越特斯拉,看颠覆全球市场的中国力量
  • 大语言模型微调方法详解【全量微调、PEFT、LoRA、Adapter】
  • Rust 力扣 - 2090. 半径为 k 的子数组平均值
  • 低压电容补偿不用时会有电流损耗吗?
  • Lampiao靶机入侵实战
  • 计算机考研,选择西安交通大学还是哈工大?
  • 吞吐量最高飙升20倍!破解强化学习训练部署难题