Spring 框架基础教程(Day03)

一. AOP 核心概念

  • 作用:在不修改原始代码的前提下增强方法功能,通过代理模式实现。
  • 核心术语
    • 连接点(JoinPoint):程序执行的任意位置(Spring AOP 中特指方法执行)。
    • 切入点(Pointcut):匹配连接点的表达式(如 execution(* com.itheima.service.*.*(..)))。
    • 通知(Advice):增强逻辑(如前置、后置、环绕等)。
    • 切面(Aspect):通知与切入点的绑定关系。
    • 目标对象(Target):被代理的原始对象。
    • 代理(Proxy):Spring 自动生成的增强对象。

以下将逐步讲解上述代码示例中使用 Spring AOP 统计 SQL 执行时间和接口执行时间的实现原理和代码细节。

1. 开启 AOP 功能

package com.example.demo;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@EnableAspectJAutoProxy
public class AspectConfig {}
  • @Configuration:这是 Spring 的注解,用于将 AspectConfig 类标记为配置类。配置类类似于传统的 XML 配置文件,它可以定义 Bean 以及其他配置信息。
  • @EnableAspectJAutoProxy:该注解用于开启 Spring AOP 的自动代理功能。开启后,Spring 会自动检测并创建代理对象,以便在目标方法执行前后插入切面逻辑。

2. 统计接口执行时间

  • @Aspect:将 InterfaceTimeAspect 类标记为切面类。切面类中定义了切入点和通知,用于在目标方法执行前后插入额外的逻辑。
  • @Component:将该类注册为 Spring 的组件,这样 Spring 容器才能管理它。
  • @Around:这是一个环绕通知注解,它可以在目标方法执行前后都执行额外的逻辑。"execution(* com.example.demo.controller.*.*(..))" 是切入点表达式,表示匹配 com.example.demo.controller 包下的所有类的所有方法。
  • ProceedingJoinPoint:在环绕通知中,ProceedingJoinPoint 用于调用目标方法。pjp.proceed() 会执行目标方法,并返回目标方法的返回值。
  • 统计时间:在调用目标方法前后分别记录时间,计算差值得到执行时间,并使用 Logger 输出日志。

3. 统计 SQL 执行时间

package com.example.demo;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Aspect
@Component
public class SqlTimeAspect {private static final Logger logger = LoggerFactory.getLogger(SqlTimeAspect.class);@Around("execution(* com.example.demo.dao.*.*(..))")public Object aroundSqlMethod(ProceedingJoinPoint pjp) throws Throwable {long startTime = System.currentTimeMillis();Object result = pjp.proceed();long endTime = System.currentTimeMillis();logger.info("SQL 方法 {} 执行时间: {} ms", pjp.getSignature().toShortString(), endTime - startTime);return result;}
}
  • 该类的结构和 InterfaceTimeAspect 类类似,只是切入点表达式不同。"execution(* com.example.demo.dao.*.*(..))" 表示匹配 com.example.demo.dao 包下的所有类的所有方法,用于统计 SQL 方法的执行时间。

4. 示例控制器

package com.example.demo.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class DemoController {@GetMapping("/test")public String test() {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}return "Test Success";}
}
  • @RestController:将该类标记为 RESTful 风格的控制器,用于处理 HTTP 请求。
  • @GetMapping("/test"):定义一个 GET 请求的映射路径 /test。当客户端访问该路径时,会执行 test 方法。
  • Thread.sleep(200):模拟接口处理的耗时操作。

5. 示例 DAO 类

  • @Repository:将该类标记为数据访问对象(DAO),Spring 会自动处理该类的异常转换。
  • executeSql 方法:模拟 SQL 执行的耗时操作。

6. 示例服务类

package com.example.demo.service;import com.example.demo.dao.DemoDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class DemoService {@Autowiredprivate DemoDao demoDao;public void callDaoMethod() {demoDao.executeSql();}
}
  • @Service:将该类标记为服务层组件,用于处理业务逻辑。
  • @Autowired:自动注入 DemoDao 实例,实现依赖注入。
  • callDaoMethod 方法:调用 DemoDao 的 executeSql 方法。

7. Spring Boot 启动类

package com.example.demo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

@SpringBootApplication:这是一个组合注解,包含了 @Configuration@EnableAutoConfiguration 和 @ComponentScan,用于启动 Spring Boot 应用。

  • SpringApplication.run(DemoApplication.class, args):启动 Spring Boot 应用。

总结

通过上述代码,我们使用 Spring AOP 实现了对接口和 SQL 方法执行时间的统计。核心是定义切面类,使用 @Around 环绕通知在目标方法执行前后记录时间,并输出日志。切入点表达式用于指定要拦截的方法。这种方式可以在不修改原有业务逻辑的前提下,添加额外的统计功能。

2. AOP 配置与实现

2.1 切入点表达式
  • 语法execution(修饰符? 返回值 包名.类名.方法名(参数) 异常?)
  • 通配符
    • *:匹配任意符号(如 * com.itheima.dao.*.*(..))。
    • ..:匹配多级路径或任意参数(如 com..*Service.*(..))。
    • +:匹配子类类型(如 *Service+)。
  • 示例
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")
    
2.2 通知类型
类型注解执行时机参数 / 返回值
前置通知@Before方法执行前JoinPoint 获取参数
后置通知@After方法执行后(无论是否异常)JoinPoint 获取参数
环绕通知@Around方法前后,需调用 proceed()ProceedingJoinPoint 控制原始方法调用
返回后通知@AfterReturning方法正常返回后returning 属性获取返回值
异常后通知@AfterThrowing方法抛出异常后throwing 属性获取异常

查缺补漏

  • 环绕通知必须依赖 ProceedingJoinPoint,否则无法调用原始方法。
  • 返回后通知异常后通知的参数名需与注解属性一致(如 @AfterReturning(returning = "ret"))。
2.3 通知数据获取
  • 参数
    • 非环绕通知:JoinPoint.getArgs()
    • 环绕通知:ProceedingJoinPoint.getArgs()(可修改参数)。
  • 返回值
    • 环绕通知:Object ret = pjp.proceed();
    • 返回后通知:@AfterReturning(returning = "ret")
  • 异常
    • 环绕通知:通过 try-catch 捕获。
    • 异常后通知:@AfterThrowing(throwing = "ex")
3. 事务管理
3.1 核心组件
  • @Transactional:标记业务方法,支持事务属性配置。
  • PlatformTransactionManager:事务管理器(如 DataSourceTransactionManager)。
3.2 事务属性
属性说明示例
readOnly是否为只读事务(默认 false@Transactional(readOnly = true)
timeout超时时间(秒),-1表示永不超时@Transactional(timeout = 5)
isolation隔离级别(如 Isolation.DEFAULTIsolation.REPEATABLE_READ@Transactional(isolation = Isolation.REPEATABLE_READ)
propagation传播行为(如 REQUIRED(默认)、REQUIRES_NEW@Transactional(propagation = Propagation.REQUIRES_NEW)
rollbackFor指定回滚的异常类型(默认仅 RuntimeException 和 Error@Transactional(rollbackFor = IOException.class)
3.3 传播行为
类型说明场景
REQUIRED若当前有事务则加入,否则新建(默认)转账操作(减钱与加钱同一事务)
REQUIRES_NEW强制新建事务,与当前事务独立日志记录(不随转账失败回滚)
NESTED在当前事务中嵌套保存点,回滚到保存点而非完全回滚部分回滚场景

查缺补漏

  • 事务未生效:确保方法被 Spring 管理,且未被 final/private 修饰。
  • 异常回滚:默认仅回滚 RuntimeException 和 Error,需通过 rollbackFor 配置其他异常。
4. 案例应用
4.1 业务层效率监控
  • 实现:使用环绕通知统计万次方法执行时间。
  • 关键代码
    @Around("servicePt()")
    public Object runSpeed(ProceedingJoinPoint pjp) throws Throwable {long start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {pjp.proceed();}System.out.println("万次执行时间: " + (System.currentTimeMillis() - start) + "ms");return null;
    }
    
4.2 密码空格处理
  • 实现:通过环绕通知过滤参数中的空格。
  • 关键代码
    @Around("pt()")
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {Object[] args = pjp.getArgs();for (int i = 0; i < args.length; i++) {if (args[i] instanceof String) {args[i] = ((String) args[i]).trim();}}return pjp.proceed(args);
    }
    
4.3 转账事务管理
  • 实现:通过 @Transactional 确保转账原子性,结合 REQUIRES_NEW 实现日志独立事务。
  • 关键配置
    @Service
    public class AccountServiceImpl {@Transactionalpublic void transfer(String out, String in, Double money) {accountDao.outMoney(out, money);accountDao.inMoney(in, money);}
    }@Service
    public class LogServiceImpl {@Transactional(propagation = Propagation.REQUIRES_NEW)public void log(String info) {logDao.log(info);}
    }
    
5. 查缺补漏
  1. AOP 代理机制

    • JDK 动态代理:基于接口,性能较高。
    • CGLIB 代理:基于子类,适用于无接口的类。
    • 默认选择:优先 JDK 动态代理,无接口时使用 CGLIB。
  2. 事务隔离级别

    • READ_UNCOMMITTED:最低级别,可能读到未提交数据。
    • READ_COMMITTED:解决脏读(Oracle 默认)。
    • REPEATABLE_READ:解决不可重复读(MySQL 默认)。
    • SERIALIZABLE:最高级别,完全串行化。
  3. 事务超时

    • 单位为秒,超时后自动回滚。
    • 需根据业务复杂度合理设置(如数据库慢查询阈值)。
  4. 常见问题

    • AOP 不生效:检查 @EnableAspectJAutoProxy 是否开启,包扫描路径是否正确。
    • 事务不回滚:确保异常类型为 RuntimeException 或通过 rollbackFor 显式配置。
6. 总结
  • AOP 通过代理模式实现非侵入式增强,核心是切入点与通知的绑定。
  • 事务管理 通过 @Transactional 和事务管理器确保数据一致性,传播行为和属性配置是关键。
  • 实践 中需结合业务场景选择合适的通知类型和事务策略,避免性能问题和数据不一致。

AOP(面向切面编程)的工作流程

AOP(面向切面编程)的工作流程可以分为以下几个关键步骤,下面为你详细介绍:

1. 定义切面和通知

  • 切面(Aspect):切面是通知和切入点的结合,它定义了在哪些连接点上执行何种通知。在 Spring AOP 中,通常使用 Java 类来定义切面,并使用 @Aspect 注解标记该类。
  • 通知(Advice):通知是切面在特定连接点执行的操作。Spring AOP 支持多种类型的通知,如前置通知(@Before)、后置通知(@After)、环绕通知(@Around)、返回后通知(@AfterReturning)和异常后通知(@AfterThrowing)。
  • 示例代码
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;@Aspect
@Component
public class LoggingAspect {@Pointcut("execution(* com.example.service.*.*(..))")public void serviceMethods() {}@Before("serviceMethods()")public void beforeServiceMethod() {System.out.println("Before service method execution");}
}

在上述代码中,LoggingAspect 类是一个切面,serviceMethods() 是切入点,beforeServiceMethod() 是前置通知。

2. 定义切入点

  • 切入点(Pointcut):切入点定义了哪些连接点会被拦截。连接点是程序执行过程中的某个特定位置,如方法调用、异常抛出等。在 Spring AOP 中,通常使用切入点表达式来定义切入点。
  • 切入点表达式:常用的切入点表达式类型是 execution,它可以匹配方法的执行。例如,execution(* com.example.service.*.*(..)) 表示匹配 com.example.service 包下所有类的所有方法。
  • 示例

在上述代码中,repositoryMethods() 定义了一个切入点,匹配 com.example.repository 包下所有类的所有方法。

3. Spring 容器启动

  • 创建容器:当 Spring 应用程序启动时,会创建 Spring 容器。容器负责管理所有的 Bean,包括切面和目标对象。
  • 扫描组件:Spring 容器会扫描配置类或 XML 配置文件中指定的包,查找带有 @Component@Service@Repository 等注解的类,并将它们注册为 Bean。
  • 开启 AOP 自动代理:需要在配置类中使用 @EnableAspectJAutoProxy 注解开启 AOP 自动代理功能,这样 Spring 容器会自动为目标对象创建代理。
  • 示例代码
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy
public class AppConfig {public static void main(String[] args) {AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);// 其他操作context.close();}
}

在上述代码中,AppConfig 是配置类,@ComponentScan 用于扫描指定包下的组件,@EnableAspectJAutoProxy 开启 AOP 自动代理。

4. 创建代理对象

  • 代理类型:Spring AOP 支持两种代理类型,JDK 动态代理和 CGLIB 代理。
    • JDK 动态代理:基于接口实现,当目标对象实现了接口时,Spring 会使用 JDK 动态代理。
    • CGLIB 代理:基于继承实现,当目标对象没有实现接口时,Spring 会使用 CGLIB 代理。
  • 创建代理:Spring 容器在创建目标对象时,会根据切入点表达式判断是否需要为该对象创建代理。如果需要,会根据代理类型创建相应的代理对象。
  • 示例:假设 UserService 是一个实现了 UserServiceInterface 的类,Spring 会为 UserService 创建一个 JDK 动态代理对象。

5. 目标方法调用

  • 调用代理对象:当客户端调用目标对象的方法时,实际上调用的是代理对象的方法。
  • 执行通知:代理对象会根据切入点表达式和通知类型,在目标方法执行前后或抛出异常时执行相应的通知。
  • 示例:当调用 UserService 的 addUser 方法时,代理对象会先执行 LoggingAspect 中的前置通知 beforeServiceMethod(),然后再调用目标方法 addUser()

6. 通知执行

  • 前置通知(@Before:在目标方法执行之前执行。
  • 后置通知(@After:在目标方法执行之后执行,无论目标方法是否抛出异常。
  • 环绕通知(@Around:在目标方法执行前后都可以执行额外的逻辑,并且可以控制目标方法的执行。
  • 返回后通知(@AfterReturning:在目标方法正常返回后执行。
  • 异常后通知(@AfterThrowing:在目标方法抛出异常后执行。
  • 示例代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;@Aspect
@Component
public class AroundLoggingAspect {@Around("execution(* com.example.service.*.*(..))")public Object aroundServiceMethod(ProceedingJoinPoint pjp) throws Throwable {System.out.println("Before method execution");Object result = pjp.proceed();System.out.println("After method execution");return result;}
}

在上述代码中,aroundServiceMethod() 是一个环绕通知,在目标方法执行前后分别输出日志。

7. 返回结果

  • 目标方法返回:目标方法执行完毕后,将结果返回给代理对象。
  • 代理对象返回:代理对象将结果返回给客户端。

总结

AOP 的工作流程主要包括定义切面和通知、定义切入点、Spring 容器启动、创建代理对象、目标方法调用、通知执行和返回结果等步骤。通过这些步骤,AOP 可以在不修改原有业务逻辑的前提下,在目标方法的特定位置插入额外的逻辑,实现诸如日志记录、事务管理等功能。

Spring 事务

1. 事务的基本概念
  • 事务(Transaction):数据库操作的最小逻辑单元,确保一系列操作要么全部成功,要么全部失败。
  • ACID 特性
    • 原子性(Atomicity):事务中的操作要么全部完成,要么全部不完成。
    • 一致性(Consistency):事务执行前后,数据的完整性约束未被破坏。
    • 隔离性(Isolation):多个事务并发执行时,彼此不可见(不同隔离级别有不同表现)。
    • 持久性(Durability):事务提交后,数据永久保存到数据库。
2. Spring 事务管理

Spring 提供两种事务管理方式:声明式事务(推荐)和编程式事务

2.1 声明式事务
  • 通过注解实现:使用 @Transactional 注解标记业务方法。
  • 优点
    • 解耦业务逻辑与事务管理。
    • 无需手动编写事务提交 / 回滚代码。
  • 示例
    @Service
    public class AccountService {@Autowiredprivate AccountDao accountDao;@Transactional // 标记事务public void transfer(String from, String to, double amount) {accountDao.withdraw(from, amount);accountDao.deposit(to, amount);}
    }
    
2.2 编程式事务
  • 通过 API 手动控制:使用 TransactionTemplate 或 PlatformTransactionManager
  • 适用场景:复杂的事务控制逻辑(如嵌套事务)。
  • 示例
    @Service
    public class OrderService {@Autowiredprivate TransactionTemplate transactionTemplate;public void processOrder() {transactionTemplate.execute(status -> {// 业务逻辑if (shouldRollback) {status.setRollbackOnly();}return null;});}
    }
    
3. 事务管理器(PlatformTransactionManager)
  • 作用:协调事务的提交与回滚。
  • 常用实现类
    • DataSourceTransactionManager:适用于 JDBC/MyBatis 等基于 JDBC 的持久化技术。
    • HibernateTransactionManager:适用于 Hibernate。
  • 配置示例(Spring Boot):
    @Configuration
    @EnableTransactionManagement
    public class TransactionConfig {@Beanpublic PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);}
    }
    
4. 事务传播行为(Propagation)

定义事务协调员对事务管理员携带事务的处理态度。

传播行为说明
REQUIRED默认值。若当前有事务则加入,否则新建事务。
REQUIRES_NEW强制新建事务,当前事务挂起。
NESTED在当前事务中嵌套保存点,回滚时仅回滚到保存点。
SUPPORTS若当前有事务则加入,否则以非事务方式执行。
NOT_SUPPORTED以非事务方式执行,挂起当前事务。

示例

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOperation() {// 日志记录(独立事务)
}
5. 事务隔离级别(Isolation)

定义事务之间的可见性。

隔离级别说明
DEFAULT使用数据库默认隔离级别(MySQL 默认 REPEATABLE_READ)。
READ_UNCOMMITTED允许脏读、不可重复读、幻读。
READ_COMMITTED避免脏读,但仍有不可重复读、幻读。
REPEATABLE_READ避免脏读、不可重复读,但仍有幻读。
SERIALIZABLE最高级别,完全串行化,避免所有并发问题。

示例

@Transactional(isolation = Isolation.REPEATABLE_READ)
public void queryData() {// 查询操作
}
6. 事务属性配置
  • timeout:事务超时时间(秒),超时自动回滚(默认 -1 表示永不超时)。
  • readOnly:标记为只读事务(默认 false),可优化数据库性能。
  • rollbackFor:指定触发回滚的异常类型(默认仅 RuntimeException 和 Error)。

示例

@Transactional(timeout = 5, readOnly = true, rollbackFor = IOException.class)
public void readData() throws IOException {// 业务逻辑
}
7. 声明式事务实现步骤
  1. 添加依赖
    <dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId>
    </dependency>
    
  2. 启用事务管理
    @Configuration
    @EnableTransactionManagement
    public class AppConfig {// 配置事务管理器
    }
    
  3. 标记业务方法
    @Service
    public class UserService {@Transactionalpublic void updateUser(User user) {// 业务逻辑}
    }
    
8. 注意事项
  • 事务未生效场景
    • 方法非 public
    • 方法内部调用(非通过代理对象调用)。
    • 异常被捕获未抛出。
  • 事务范围
    • 避免事务方法过大(降低并发性能)。
    • 避免在事务中执行耗时操作(如文件读写)。
9. 总结
  • 声明式事务是 Spring 事务管理的核心,通过 @Transactional 注解实现解耦。
  • 事务管理器负责协调事务的提交与回滚。
  • 传播行为隔离级别需根据业务场景合理选择,避免数据不一致或性能问题。

Spring 事务角色详解

在 Spring 事务管理中,事务角色分为 事务管理员(Transaction Administrator) 和 事务协调员(Transaction Coordinator),二者通过协作保证数据一致性。

1. 事务管理员
  • 定义:发起事务的方法,通常位于业务层(Service 层)。
  • 职责
    • 开启事务。
    • 控制事务的提交或回滚。
    • 管理事务的传播行为和隔离级别。
  • 示例
    @Service
    public class AccountService {@Autowiredprivate AccountDao accountDao;@Transactional // 事务管理员public void transfer(String from, String to, double amount) {accountDao.withdraw(from, amount); // 事务协调员accountDao.deposit(to, amount);    // 事务协调员}
    }
    
  • 关键注解@Transactional 标注在业务方法上。
2. 事务协调员
  • 定义:加入事务管理员发起的事务的方法,通常位于数据层(DAO 层)。
  • 职责
    • 参与事务管理员的事务。
    • 执行具体的数据操作(如 SQL 增删改)。
  • 示例
    @Repository
    public class AccountDao {@Transactional // 事务协调员(默认传播行为为 REQUIRED)public void withdraw(String account, double amount) {// 执行扣款 SQL}
    }
    
  • 传播行为:默认使用 Propagation.REQUIRED,即加入当前事务。
3. 核心协作流程
  1. 事务管理员开启事务
    • 业务层方法标注 @Transactional,Spring 自动开启事务。
  2. 事务协调员加入事务
    • 数据层方法被调用时,若当前存在事务(管理员开启的),则加入该事务。
  3. 异常处理
    • 若业务层方法抛出 RuntimeException 或 Error,事务管理员触发回滚,所有协调员操作均回滚。
    • 若协调员抛出受检异常且未被捕获,事务管理员也会回滚。
4. 事务传播行为与角色关系
传播行为事务管理员事务协调员示例场景
REQUIRED开启新事务加入当前事务(默认)转账操作(扣款与存款同一事务)
REQUIRES_NEW开启新事务强制新建事务,挂起当前事务日志记录(独立于主事务)
NESTED开启新事务在当前事务中创建保存点,回滚到保存点部分回滚(如订单支付与库存扣减)
5. 事务角色配置
  • 事务管理员
    • 标注 @Transactional 在业务方法上,设置传播行为、隔离级别等。
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ)
    public void processOrder() {// 业务逻辑
    }
    
  • 事务协调员
    • 数据层方法无需显式标注 @Transactional(默认加入当前事务)。
    • 如需独立事务,显式设置 @Transactional(propagation = Propagation.REQUIRES_NEW)
6. 最佳实践
  1. 事务应尽可能放在业务层
    • 业务层方法作为事务管理员,控制多个数据层操作的原子性。
  2. 避免数据层单独开启事务
    • 数据层方法默认加入业务层事务,减少事务嵌套。
  3. 合理选择传播行为
    • 日志记录、缓存更新等非核心操作使用 REQUIRES_NEW,避免主事务回滚影响。
7. 总结
  • 事务管理员是事务的发起者和管理者,位于业务层。
  • 事务协调员是事务的参与者,位于数据层,默认加入管理员的事务。
  • 通过 @Transactional 和传播行为配置,确保复杂业务操作的数据一致性。

Spring 事务失效场景与解决方案

在 Spring 声明式事务中,@Transactional 注解可能因配置或代码逻辑问题导致事务失效。以下是常见失效场景及解决方案:

1. 方法非 public
  • 原因:Spring AOP 默认仅对 public 方法应用事务。
  • 示例
    @Service
    public class UserService {// 事务失效(非 public)@Transactionalvoid updateUser(User user) {// 业务逻辑}
    }
    
  • 解决方案:将方法声明为 public
2. 异常被捕获未抛出
  • 原因:事务默认仅对 RuntimeException 和 Error 回滚,若异常被捕获且未抛出,事务不会回滚。
  • 示例
    @Transactional
    public void transfer() {try {// 业务逻辑int i = 1 / 0; // 抛出异常} catch (Exception e) {// 异常被捕获,未抛出e.printStackTrace();}
    }
    
  • 解决方案
    • 抛出 RuntimeException 或 Error
    • 使用 @Transactional(rollbackFor = Exception.class) 强制回滚所有异常。
3. 传播行为配置错误
  • 原因:若事务协调员配置了 REQUIRES_NEW 但未正确处理异常,可能导致主事务回滚而协调员事务提交。
  • 示例
    @Service
    public class OrderService {@Autowiredprivate PaymentService paymentService;@Transactionalpublic void createOrder() {paymentService.charge(); // 若 charge 异常且未抛出,主事务回滚,但 charge 的事务已提交}
    }@Service
    public class PaymentService {@Transactional(propagation = Propagation.REQUIRES_NEW)public void charge() {// 业务逻辑throw new RuntimeException("Payment failed");}
    }
    
  • 解决方案:确保异常传播到事务管理员,或在协调员中显式回滚。
4. 类内部方法调用
  • 原因:通过 this 调用类内部方法时,未经过 Spring 代理,事务不生效。
  • 示例
    @Service
    public class UserService {public void updateUser() {this.saveUser(); // 内部调用,事务失效}@Transactionalpublic void saveUser() {// 业务逻辑}
    }
    
  • 解决方案
    • 通过依赖注入自己(代理对象)。
    • 使用 AopContext.currentProxy() 获取代理对象(需配置 exposeProxy=true)。
5. 未正确配置事务管理器
  • 原因:未在 Spring 容器中注册 PlatformTransactionManager
  • 示例
    @Configuration
    public class TransactionConfig {// 缺少 @Bean PlatformTransactionManager
    }
    
  • 解决方案
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
    }
    
6. 注解使用错误
  • 原因:错误地在接口或非 Spring 管理的类上使用 @Transactional
  • 示例
    public interface UserService {@Transactional // 无效(接口非 Spring 组件)void updateUser();
    }
    
  • 解决方案:仅在 Spring 管理的组件(如 @Service@Component)中使用 @Transactional
7. 数据库引擎不支持事务
  • 原因:例如 MySQL 的 MyISAM 引擎不支持事务。
  • 解决方案:使用 InnoDB 引擎。
8. 方法使用 final 或 static
  • 原因:Spring 代理无法覆盖 final 或 static 方法。
  • 示例
    @Service
    public class UserService {@Transactionalpublic final void updateUser() { // 事务失效// 业务逻辑}
    }
    
  • 解决方案:移除 final 或 static 修饰符。
9. 事务超时设置不合理
  • 原因:事务执行时间超过 timeout 设置,自动回滚。
  • 示例
    @Transactional(timeout = 1) // 1 秒超时
    public void longRunningMethod() {// 执行耗时操作
    }
    
  • 解决方案:根据业务需求调整 timeout 值。
10. 事务未被 Spring 管理
  • 原因:目标对象未被 Spring 容器管理(如手动 new 对象)。
  • 示例
    public class UserService {@Transactionalpublic void updateUser() {// 业务逻辑}
    }// 手动创建对象,事务失效
    UserService userService = new UserService();
    userService.updateUser();
    
  • 解决方案:通过 Spring 容器获取对象(如 @Autowired)。

避免事务失效的最佳实践

  1. 方法声明为 public
  2. 避免捕获异常不抛出,或明确配置 rollbackFor
  3. 合理使用传播行为(如 REQUIREDREQUIRES_NEW)。
  4. 通过代理对象调用方法,避免类内部直接调用。
  5. 正确配置事务管理器
  6. 确保数据库引擎支持事务(如 InnoDB)。
  7. 避免使用 final/static 修饰事务方法

总结

事务失效的核心原因通常是代理机制未生效、异常未正确传播或配置错误。通过遵循最佳实践和仔细检查代码逻辑,可有效避免这些问题。

1. Spring AOP 代理机制的限制

Spring 通过 AOP 实现声明式事务,默认使用 JDK 动态代理 或 CGLIB 代理

  • JDK 动态代理
    • 基于接口实现,只能代理接口中的 public 方法。
    • 非 public 方法(如 protectedprivate)无法被代理,事务注解失效。
  • CGLIB 代理
    • 基于继承,可代理类的 public 方法。
    • 非 public 方法(如 finalstatic)无法被覆盖,事务注解失效。

2. 为什么非 public 方法事务失效?

  • 访问控制限制
    • JDK 代理只能调用接口中声明的 public 方法。
    • CGLIB 代理默认仅处理 public 方法,非 public 方法无法被增强。
  • Spring 设计哲学
    • Spring 推荐面向接口编程,业务逻辑应通过接口暴露,而非直接操作实现类。
    • 非 public 方法违背了这一原则,导致事务无法通过代理生效。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/39592.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

欢迎来到未来:探索 Dify 开源大语言模型应用开发平台

欢迎来到未来&#xff1a;探索 Dify 开源大语言模型应用开发平台 如果你对 AI 世界有所耳闻&#xff0c;那么你一定听说过大语言模型&#xff08;LLM&#xff09;。这些智能巨兽能够生成文本、回答问题、甚至编写代码&#xff01;但是&#xff0c;如何将它们变成真正的实用工具…

计算机工具基础(七)——Git

Git 本系列博客为《Missing in CS Class(2020)》课程笔记 Git是一种分布式版本控制系统&#xff0c;被其跟踪的文件可被查询精细到行的修改记录、回退版本、建立分支等 模型 一般流程&#xff1a;工作区 → \to →暂存区 → \to →仓库(本地 → \to →远端) 工作区&#xff1…

uniapp动态循环表单校验失败:初始值校验

问题现象 &#x1f4a5; 在实现动态增减的单价输入表单时&#xff08;基于uv-form组件&#xff09;&#xff0c;遇到以下诡异现象&#xff1a; <uv-input>的v-model绑定初始值为数字类型时&#xff0c;required规则失效 ❌数字类型与字符串类型校验表现不一致 &#x1…

前端框架学习路径与注意事项

学习前端框架是一个系统化的过程&#xff0c;需要结合理论、实践和工具链的综合掌握。以下是学习路径的关键方面和注意事项&#xff1a; 一、学习路径的核心方面 1. 基础概念与核心思想 组件化开发&#xff1a;理解组件的作用&#xff08;复用性、隔离性&#xff09;、组件通信…

【Python机器学习】3.5. 决策树实战:基于Iris数据集

喜欢的话别忘了点赞、收藏加关注哦&#xff08;关注即可查看全文&#xff09;&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 本文紧承 3.1. 决策树理论(基础) 和 3.2. 决策树理论(进阶)&#xff0c;没看过的建议先看理论分…

Unity2022发布Webgl2微信小游戏部分真机黑屏

复现规律&#xff1a; Unity PlayerSetting中取消勾选ShowSplashScreen 分析&#xff1a; 在Unity中&#xff0c;Splash Screen&#xff08;启动画面&#xff09; 不仅是视觉上的加载动画&#xff0c;还承担了关键的引擎初始化、资源预加载和渲染环境准备等底层逻辑。禁用后导…

docker desktop 集成WSL Ubuntu22.04

Windows docker desktop 设置WSL ubuntu 22.04启用与其他发行版的集成 Windows docker desktop 安装参考 wsl ubuntu 22.04 查看我宿主机的docker desktop 容器全部的信息 wsl -d Ubuntu-22.04 -u root

快速入手-基于Django的主子表间操作mysql(五)

1、如果该表中存在外键&#xff0c;结合实际业务情况&#xff0c;那可以这么写&#xff1a; 2、针对特殊的字典类型&#xff0c;可以这么定义 3、获取元组中的字典值和子表中的value值方法 4、对应的前端页面写法

使用cursor开发java案例——springboot整合elasticsearch

安装elasticsearch 打开cursor&#xff0c;输入如下提示词 使用springboot整合elasticsearch。其中elasticsearch服务器ip&#xff1a;192.168.236.134 管理员用户名elastic 管理员密码 PdQy_xfR2yLhpok*MK_ 监听端口9200点Accept all 使用idea打开生成的项目 &#xff0…

Deepseek结合企业数据挖掘平台能够给企业提升哪些效益?

Deepseek&#xff08;深度求索&#xff09;作为智能系统&#xff0c;在政务办公领域可通过AI技术优化流程、提升效率&#xff0c;具体应用场景分析如下&#xff1a; 1. 智能公文处理与流转 自动分类与审核 利用NLP解析公文内容&#xff0c;自动分类&#xff08;如请示、报告、通…

vite中sass警告JS API过期

在Vite创建项目中引入Sass弹出The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0 vite中sass警告JS API过期 The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0 警告提示表明你当前正在使用的 Dart Sass 版本中&#xff0c;旧的…

jenkins+1panel面板java运行环境自动化部署java项目

本文章不包含1panel面板安装、jenkins部署、jenkins连接git服务器等操作教程&#xff0c;如有需要可以抽空后期补上 jenkins安装插件Publish Over SSH 在系统配置添加服务器 查看项目的工作空间 项目Configure->构Post Steps选择Send files or execute commands over SSH…

DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能

前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏+关注哦 💕 目录 DeepSeek 助力 Vue3 开发:打造丝滑的表格(Table)之添加导出数据功能📚页面效果📚指令输入�…

SpringBoot与Redisson整合,用注解方式解决分布式锁的使用问题

文章引用&#xff1a;https://mp.weixin.qq.com/s/XgdKE2rBKL0-nFk2NJPuyg 一、单个服务 1.代码 该接口的作用是累加一个值&#xff0c;访问一次该值加1 RestController public class LockController {Autowiredprivate StringRedisTemplate stringRedisTemplate;GetMappin…

SpringBoot 统一功能处理

目录 1. 拦截器 1.1 什么是拦截器 1.2 定义拦截器 1.3 注册拦截器 1.3.1 拦截路径 1.4 登录校验 - 拦截器 1.4.1 定义拦截器 1.4.2 注册拦截器 1.4.3 前端代码 1.5 DisPatchServlet 底层源码解析 2. 统一结果返回格式 2.1 ResponseBodyAdvice 2.1.1 存在问题1 - 原本…

电机控制常见面试问题(十八)

文章目录 一.电机控制高级拓扑结构1.LLC 二.谈谈电压器饱和后果三.电压器绕组连接方式的影响四.有源逆变的条件 一.电机控制高级拓扑结构 1.LLC LLC是什么&#xff1f;—— 一个会"变魔术"的电源盒子 想象你有一个魔法盒子&#xff0c;能把电池的电压变大或变小&…

如何解决用户名文件夹是中文导致的识别不到路径,获取不到ssh密匙

如果你不想更改你的文件夹用户名导致之前配置的环境变量及相关软件失效&#xff0c;那么只需要指定自定义路径生成密钥 完整解决方案 1. 设置一个简单的 HOME 路径 由于你的用户名包含中文字符&#xff0c;导致默认路径 无法正确解析。我们可以通过修改 HOME 环境变量&#…

Python入门基础

python基础类型转换 str()与int()类型转换 name 张三 age 20 print(type(name),type(age))print(我叫name 今年&#xff0c; str(age)岁 )a10 b198.8 cFalse print(type(a),type(b),type(c)) print(str(a),str(b),str(c))s1 128 f198.7 s276.77 ffTrue s3hello print(type(s…

GithubPages+自定义域名+Cloudfare加速+浏览器收录(2025最新排坑)

前言 最近刷到一个小视频&#xff0c;讲述了选择域名选择的三宗罪&#xff0c;分别是 不要使用 .net&#xff0c;因为它价格贵&#xff0c;但是在顶级域名中的 SEO 效果却不是很好&#xff0c;也就是性价比很低不要使用 .cn&#xff0c;因为国外访问该网站可能会很慢&#xf…

监控IP,网站将异常情况通过飞书机器人发至指定群内

界面如下&#xff0c;丑是丑了点&#xff0c;但主打一个实用。 主要就是通过ping&#xff0c;就是一直在ping&#xff0c;当不通的时候&#xff0c;就根据你设置的报警时间&#xff0c;主要是利用飞书机器人来给飞书指定群里发异常信息报警。 直接上代码 import subprocess i…