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使用场景:
- 日志记录(Logging):在方法调用前后记录日志信息,用于跟踪方法执行情况、性能监控或调试。
- 权限检查(Security/Authorization):在方法执行前验证用户是否有权限执行该操作,比如角色检查或资源访问控制。
- 事务管理(Transaction Management):自动管理数据库事务的开启、提交或回滚,保证数据的一致性。
- 异常处理(Exception Handling):集中处理特定类型的异常,比如记录异常信息或执行特定的恢复操作。
- 性能监控(Performance Monitoring):监控方法执行时间,帮助识别和优化性能瓶颈。
- 缓存(Caching):自动缓存方法的返回结果,减少不必要的数据库查询或其他耗时操作。
- 参数校验和转换(Parameter Validation and Conversion):在方法调用前对参数进行校验或转换,确保符合业务逻辑要求。
- API调用统计(API Call Tracking):记录API的调用次数、频率等,用于分析和优化。
- SLF4J、Logback、Log4j等日志框架集成:通过AOP可以在不修改业务代码的情况下,灵活地切换或增强日志框架的功能。
- 自定义注解的处理:使用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();
// 更新实体数据
}
在这个例子中:
@Transactional
注解标记了这个方法是在一个事务中执行的。entityManager.createQuery
创建了一个JPA查询,用于从数据库中检索Entity
实体。"SELECT e FROM Entity e WHERE e.id = :id"
是JPA查询的字符串,它指定了从Entity
实体类中选择与给定id
匹配的实体。.setParameter("id", entity.getId())
设置了查询参数id
的值。.setLockMode(LockModeType.PESSIMISTIC_WRITE)
设置了锁模式为悲观写锁,这会触发数据库层面的SELECT FOR UPDATE
操作。.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会为这个方法创建一个代理,并在这个方法执行时管理事务。然而,deductAmount
和addAmount
是私有方法,它们被transferMoney
内部调用。由于这些私有方法不是通过代理调用的(因为它们是同一个类内部的直接调用),Spring事务管理器不会为这些私有方法的调用添加事务管理逻辑。
这意味着如果在deductAmount
或addAmount
中的数据库操作失败,事务不会回滚,因为这些操作没有被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.REQUIRED
,innerMethod
会加入到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)的方式进行事务的管理。