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

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) {// 异常被捕获并处理,但重新抛出RuntimeExceptionthrow 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 {@Transactionalpublic 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 {@Transactionalpublic 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代理,拥有声明式事务管理@Transactionalpublic 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 {@Transactionalpublic 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.面试题

相关文章:

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

目录 1. AOP定义&#xff1f; 2.常见的AOP使用场景&#xff1a; 3.Spring AOP用法 3.1 Spring AOP中的几个核心概念 3.1.1 切面、切点、通知、连接点 3.1.2 切点表达式AspectJ 3.2 使用 Spring AOP 的步骤总结 3.2.1 添加依赖: 3.2.2 定义切面和切点&#xff08;切点和…...

Flutter鸿蒙next 封装对话框详解

✅近期推荐&#xff1a;求职神器 https://bbs.csdn.net/topics/619384540 &#x1f525;欢迎大家订阅系列专栏&#xff1a;flutter_鸿蒙next &#x1f4ac;淼学派语录&#xff1a;只有不断的否认自己和肯定自己&#xff0c;才能走出弯曲不平的泥泞路&#xff0c;因为平坦的大路…...

【项目实战】通过LLaMaFactory+Qwen2-VL-2B微调一个多模态医疗大模型

前言 随着多模态大模型的发展&#xff0c;其不仅限于文字处理&#xff0c;更能够在图像、视频、音频方面进行识别与理解。医疗领域中&#xff0c;医生们往往需要对各种医学图像进行处理&#xff0c;以辅助诊断和治疗。如果将多模态大模型与图像诊断相结合&#xff0c;那么这会…...

SCSI驱动与 UFS 驱动交互概况

SCSI子系统概况 SCSI&#xff08;Small Computer System Interface&#xff09;子系统是 Linux 中的一个模块化框架&#xff0c;用于提供与存储设备的通用接口。通过 SCSI 子系统&#xff0c;可以支持不同类型的存储协议&#xff08;如 UFS、SATA、SAS&#xff09;&#xff0c…...

软件工程实践项目:人事管理系统

一、项目的需求说明 通过移动设备登录app提供简单、方便的操作。根据公司原来的考勤管理制度&#xff0c;为公司不同管理层次提供相应的权限功能。通过app上面的各种标准操作&#xff0c;考勤管理无纸化的实现&#xff0c;使公司的考勤管理更加科学规范&#xff0c;从而节省考…...

不使用三方软件,win系统下禁止单个应用联网能力的详细操作教程

本篇文章主要讲解&#xff0c;在win系统环境下&#xff0c;禁止某个应用联网能力的详细操作教程&#xff0c;通过本教程您可以快速掌握自定义对单个程序联网能力的限制和禁止。 作者&#xff1a;任聪聪 日期&#xff1a;2024年10月30日 步骤一、按下win按键&#xff08;四个小方…...

近似线性可分支持向量机的原理推导

近似线性可分的意思是训练集中大部分实例点是线性可分的&#xff0c;只是一些特殊实例点的存在使得这种数据集不适用于直接使用线性可分支持向量机进行处理&#xff0c;但也没有到完全线性不可分的程度。所以近似线性可分支持向量机问题的关键就在于这些少数的特殊点。 相较于…...

Golang开发环境

Golang开发环境搭建 Go 语言开发包 国外&#xff1a;https://golang.org/dl/ 国内(推荐)&#xff1a; https://golang.google.cn/dl/ 编辑器 Golang:https://www.jetbrains.com/go/ Visual Studio Code: https://code.visualstudio.com/ 搭建 Go 语言开发环境&#xff0c;需要…...

测试华为GaussDB(DWS)数仓,并通过APISQL快速将(表、视图、存储过程)发布为API

华为数据仓库服务 数据仓库服务&#xff08;Data Warehouse Service&#xff0c;简称DWS&#xff09;是一种基于公有云基础架构和平台的在线数据处理数据库&#xff0c;提供即开即用、可扩展且完全托管的分析型数据库服务。DWS是基于华为融合数据仓库GaussDB产品的云原生服务&a…...

使用GetX实现GetPage中间件

前言 GetX 中间件&#xff08;Middleware&#xff09;是 GetX 框架中的一种机制&#xff0c;用于在页面导航时对用户进行权限控制、数据预加载、页面访问条件设置等。通过使用中间件&#xff0c;可以有效地控制用户的访问流程&#xff0c;并在适当条件下引导用户到所需页面。 这…...

Navicat 17 功能简介 | SQL 预览

Navicat 17 功能简介 | SQL 预览 随着 17 版本的发布&#xff0c;Navicat 也带来了众多的新特性&#xff0c;包括兼容更多数据库、全新的模型设计、可视化智能 BI、智能数据分析、可视化查询解释、高质量数据字典、增强用户体验、扩展MongoDB 功能、轻松固定查询结果、便捷URI …...

ubuntu、Debian离线部署gitlab

一、软件包下载 gitlab安装包下载链接 ubuntu&#xff1a; ubuntu/focal 适用于 ubuntu20系列 ubuntu/bionic 适用于 ubuntu18 系列 Debian&#xff1a; debian/buster 适用于 Debian10系列 debian/bullseye 适用于 Debian11、12系列 二、安装gitlab ubuntu需要安装一些环境…...

数据库编程 SQLITE3 Linux环境

永久存储程序数据有两种方式&#xff1a; 用文件存储用数据库存储 对于多条记录的存储而言&#xff0c;采用文件时&#xff0c;插入、删除、查找的效率都会很差&#xff0c;为了提高这些操作的效率&#xff0c;有计算机科学家设计出了数据库存储方式 一、数据库 数据库的基本…...

独孤思维:总有一双眼睛默默观察你做副业

01 独孤昨天在陪伴群&#xff0c;分享了近期小白做副业的一些困扰。 并且以自己经历作为案例&#xff0c;分享了一些经验和方法。 最后顺势推出xx博主的关于365条赚钱信息小报童专栏。 订阅后&#xff0c;可以开拓副业赚钱思路&#xff0c;避免走一些弯路。 甚至于&#x…...

医院信息化与智能化系统(10)

医院信息化与智能化系统(10) 这里只描述对应过程&#xff0c;和可能遇到的问题及解决办法以及对应的参考链接&#xff0c;并不会直接每一步详细配置 如果你想通过文字描述或代码画流程图&#xff0c;可以试试PlantUML&#xff0c;告诉GPT你的文件结构&#xff0c;让他给你对应…...

基于YOLO11/v10/v8/v5深度学习的危险驾驶行为检测识别系统设计与实现【python源码+Pyqt5界面+数据集+训练代码】

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…...

Flink CDC系列之:学习理解核心概念——Transform

Flink CDC系列之&#xff1a;学习理解核心概念——Transform Transform参数元数据字段函数比较函数逻辑函数字符串函数时间函数条件函数 示例添加计算列参考元数据列使用通配符投影所有字段添加过滤规则重新分配主键重新分配分区键指定表创建配置分类映射用户定义函数已知限制 …...

MyBatis-Plus:简化 CRUD 操作的艺术

一、关于MyBatis-Plus 1.1 简介 MyBatis-Plus 是一个基于 MyBatis 的增强工具&#xff0c;它旨在简化 MyBatis 的使用&#xff0c;提高开发效率。 ​ ‍ ‍ ‍ ​ ‍ 关于Mybatis 简介 MyBatis 是一款流行的 Java 持久层框架&#xff0c;旨在简化 Java 应用程序与数…...

Windows on ARM编译安装openBLAS

Windows on ARM编译安装openBLAS 要求下载源码OpenBLAS可以使用LLVM工具链(clang-cl和flang)从源代码为Windows on ARM(WoA)进行构建。v0.3.24版本(预构建包)的构建和测试已通过。 要求 LLVM:版本需大于等于17.0.4 LLVM版本16及以下会生成冲突的符号(如_QQ*等)。 LL…...

FPGA编程语言VHDL与Verilog的比较分析!!!

VHDL&#xff08;VHSIC硬件描述语言&#xff09;和Verilog都是用于硬件描述和FPGA编程的工业标准语言。它们在语法和设计理念上存在一些差异&#xff0c;以下是两者的比较分析&#xff1a; 1. 历史背景 VHDL&#xff1a; 开发于1980年代初期&#xff0c;最初用于美国国防部的…...

C语言——八股文(笔试面试题)

1、 什么是数组指针&#xff0c;什么是指针数组&#xff1f; 数组指针&#xff1a;指向数组的指针 指针数组&#xff1a;数组中的元素都是指针 2、 什么是位段&#xff0c;什么是联合体 位段&#xff08;Bit Field&#xff09;&#xff1a;在C语言中&#xff0c;允许在一个整数…...

解决 Oracle 数据库错误 ORA-12516:监听器无法找到匹配协议栈的处理程序

在使用 Oracle 数据库时&#xff0c;有时会遇到错误 ORA-12516&#xff0c;这个错误表明 Oracle 数据库的监听器无法为新的连接请求找到一个可用的处理程序&#xff0c;这通常是因为达到了连接数上限、配置问题或资源限制。本文将详细介绍如何解决这个问题。 一、错误描述 当…...

Flarum:简洁而强大的开源论坛软件

Flarum简介 Flarum是一款开源论坛软件&#xff0c;以其简洁、快速和易用性而闻名。它继承了esoTalk和FluxBB的优良传统&#xff0c;旨在提供一个不复杂、不臃肿的论坛体验。Flarum的核心优势在于&#xff1a; 快速、简单&#xff1a; Flarum使用PHP构建&#xff0c;易于部署&…...

方法+数组

1. 方法 1. 什么是方法 方法定义&#xff1a; // []表示可写可不写[public] [static] type name ( [type formal , type formal , ...]){方法体&#xff1b;[return value ;] }[修饰符] 返回值类型 方法名称([参数类型 形参 , 参数类型 形参 ...]){方法体代码;[return 返回值…...

驱动-----adc

在key1.c的基础上进行对adc1.c进行编写 首先将文件里面的key全部改为adc 再修改一下设备号 按键和adc的区别是什么,按键只需要按一下就触发了,并且不需要返回一个值出来, adc要初始化,启动,返回值 以下是裸机adc的代码: #include <s3c2440.h> #include "ad…...

js实现点击图片,使图片跟随鼠标移动(把注释打开是图片随机位置)

代码&#xff1a; <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>Document</title>&l…...

MacOS的powermetrics命令查看macbook笔记本的耗能情况,附带查看ANE的工作情况

什么是 powermetrics&#xff1f; powermetrics 是 macOS 系统自带的一个命令行工具&#xff0c;用于收集和分析系统能源消耗数据。通过它&#xff0c;我们可以深入了解 Mac 的硬件性能、软件行为以及能源使用情况&#xff0c;从而优化系统配置&#xff0c;提高电池续航时间。…...

字符串函数

大家好&#xff0c;今天我们来了解几个字符串函数 1.strcpy函数 这个函数是一个字符串复制函数&#xff0c;其全称为string copy&#xff0c;它可以将一个源字符数组的内容复制到目标字符数组中&#xff0c;我们需要关注几个问题&#xff0c;首先源字符串必须以&#xff3c;0…...

Java数组的地址和元素访问 C语言空指针与野指针

1. public static void main(String[] args) {int []arr{1,2,3,4,5};int numarr[0];System.out.println(num);System.out.println(arr[1]);System.out.println(arr);//[I610f87f48//[表示地址 I表示数据类型 表示间隔符号&#xff08;固定格式&#xff09;//10f87f48表示地址…...

如何在Linux系统中使用SSH进行安全连接

如何在Linux系统中使用SSH进行安全连接 SSH简介 安装SSH 在Debian/Ubuntu系统中安装 在CentOS/RHEL系统中安装 启动SSH服务 验证SSH是否安装成功 SSH配置 配置监听端口 配置登录方式 SSH客户端 安装SSH客户端 使用SSH客户端 SSH密钥认证 生成SSH密钥对 复制公钥到远程服务器…...

网店推广的平台有哪些/百度seo收录

6.1 网络应用模型 应用层概述 网络应用模型 客户/服务器&#xff08;C/S&#xff09;模型 P2P模型 6.2 DNS系统 域名 根&#xff1a;在com后面还有一个点 域名服务器 DNS服务器 注意分区 域名解析过程 递归查询&#xff1a;靠其它层 迭代查询&#xff1a;一直是本地域…...

温州哪里可以做企业网站/介绍产品的营销推文

连接数据库添加JPA 添加 persistence视图 生成的实体类会有波浪号&#xff0c;解决方法...

网站建设需求调查/广州白云区疫情实时动态

DedeCms 设置附件上传目录无效的解决办法 DedeCms已经升级到5.7版本了.. 可惜附件的目录还是不统一&#xff0c;比如我们从后台把附件目录调整为Ym(默认为Ymd) 然而我们的附件路径依然是不一样的 比如&#xff1a; Ymd代表年月日 从文章里上传 路径为:/Ym/1.jpg 从附件里上传呢…...

网站建设推广代理公司/推广运营公司哪家好

一、描述 这是个很有意思的模式。它是通过共享技术来有效地支持大量细粒度对象的复用。角色 &#xff08;1&#xff09;Flyweight&#xff1a;描述一个接口&#xff0c;通过这个接口flyweight可以接受并作用于外部状态 &#xff08;2&#xff09;ConcreteFlyweight&#xff1…...

网站做链接算侵权吗/众志seo

计算机系统的组成 (电脑入门到精通网 www.58116.cn)微型计算机由硬件系统和软件系统组成。硬件系统&#xff1a;指构成计算机的电子线路、电子元器件和机械装置等物理设备&#xff0c;它包括计算机的主机及外部设备。 (电脑入门到精通网 www.58116.cn)软件系统&#xff1a;指程…...

理县网站建设公司/中国最好的网络营销公司

资源管理器是什么&#xff1f;它电脑系统中比较重要的工具&#xff0c;它的主要负责工作是管理数据库&#xff0c;但也可以用来存储数据并且执行故障恢复。操作后系统将自行重启&#xff0c;然后恢复正常。但是这样做会导致经常卡顿&#xff0c;这严重影响了电脑的使用&#xf…...