全栈学习 ——JavaWeb(五)Spring AOP 与事务

Java Spring AOP与事务管理全解析

目录

一、Spring AOP:面向切面编程的核心思想

1. 什么是 AOP?解决什么问题?

(1)传统 OOP 的局限性

(2)AOP 的解决方案:横向抽取

2. AOP 的底层实现:动态代理

(1)JDK 动态代理(默认,针对接口)

(2)CGLIB 动态代理(针对类)

(3)Spring AOP 的代理选择逻辑

3. Spring AOP 实战:通过注解实现切面

(1)环境准备

(3)目标对象(业务类)

(4)测试 AOP 效果

(5)切入点表达式详解

4. AOP 的实际应用场景

(1)日志记录

(2)权限校验

(3)异常统一处理

二、Spring 事务管理:保证数据一致性

1. 事务的 ACID 特性

2. 传统 JDBC 事务的问题

3. Spring 声明式事务:通过注解简化管理

4. 声明式事务的实现步骤

(1)环境准备

(2)配置数据源和事务管理器

(3)使用 @Transactional 注解标识事务方法

(4)测试事务效果

5. 事务属性详解

(1)传播行为(propagation)

(2)隔离级别(isolation)

(3)超时时间(timeout)

(4)只读(readOnly)

(5)回滚策略(rollbackFor/noRollbackFor)

6. 事务的底层原理:AOP 的应用

三、项目实战:整合 AOP 与事务

1. 数据库表设计

2. 代码实现

(1)实体类

(2)DAO 层

(3)Service 层(含事务)

(4)AOP 日志切面

(5)测试类

四、总结:AOP 与事务的核心价值


在 Java 开发中,Spring 的 AOP(面向切面编程)和事务管理是提升代码模块化和数据一致性的核心技术。AOP 通过 “横向切入” 的方式解决日志、权限等跨模块问题,而事务管理则确保数据库操作的原子性、一致性。本文将从底层原理到实战应用,全面解析这两大技术,包含丰富代码示例和项目测试。

一、Spring AOP:面向切面编程的核心思想

1. 什么是 AOP?解决什么问题?

AOP(Aspect-Oriented Programming,面向切面编程)是一种通过 “横向抽取” 实现代码复用的技术。它弥补了 OOP(面向对象编程)在 “跨模块功能” 上的不足。

(1)传统 OOP 的局限性

在分层架构中,某些功能(如日志、权限校验、异常处理)会分散在各个业务方法中,导致代码冗余且难以维护。

示例:日志代码分散在业务方法中

@Service

public class UserService {

public void register(User user) {

// 日志代码(冗余)

System.out.println("开始执行register方法,参数:" + user);

// 核心业务

userDAO.save(user);

// 日志代码(冗余)

System.out.println("register方法执行结束");

}

public User query(String username) {

// 日志代码(冗余)

System.out.println("开始执行query方法,参数:" + username);

// 核心业务

User user = userDAO.findByUsername(username);

// 日志代码(冗余)

System.out.println("query方法执行结束,结果:" + user);

return user;

}

}

问题分析

  • 日志代码与业务代码混杂,违背 “单一职责原则”;
  • 若需修改日志格式,需修改所有业务方法,维护成本高。
(2)AOP 的解决方案:横向抽取

AOP 将分散在各个方法中的 “跨模块功能”(如日志)抽取为独立的 “切面”,通过配置指定在哪些方法(“切入点”)的什么时机(“通知”)执行,从而实现 “业务代码与非业务代码分离”。

AOP 核心术语

  • 切面(Aspect):封装跨模块功能的类(如日志切面、权限切面);
  • 切入点(Pointcut):指定切面作用于哪些方法(通过表达式匹配);
  • 通知(Advice):切面的具体逻辑,包括执行时机(如方法前、方法后);
  • 目标对象(Target):被切面增强的原始对象(如UserService);
  • 代理对象(Proxy):AOP 生成的包含切面逻辑的对象(实际调用的对象);
  • 连接点(Joinpoint):程序执行过程中可被切面拦截的点(如方法调用、异常抛出)。

AOP 执行流程

客户端调用代理对象的方法;

代理对象先执行切面的通知逻辑(如日志);

再调用目标对象的核心业务方法;

最后返回结果给客户端。

2. AOP 的底层实现:动态代理

Spring AOP 的底层通过动态代理技术实现:在运行时动态生成目标对象的代理对象,将切面逻辑织入代理对象中。Spring 支持两种动态代理方式:

(1)JDK 动态代理(默认,针对接口)

当目标对象实现接口时,Spring 使用 JDK 的java.lang.reflect.Proxy生成代理对象,代理对象实现相同接口。

示例:手动实现 JDK 动态代理

// 1. 业务接口

public interface UserService {

void register(User user);

}

// 2. 目标对象(实现接口)

public class UserServiceImpl implements UserService {

@Override

public void register(User user) {

System.out.println("执行注册业务:" + user);

}

}

// 3. 日志切面(横切逻辑)

public class LogHandler implements InvocationHandler {

private Object target; // 目标对象

public LogHandler(Object target) {

this.target = target;

}

// 代理对象的方法执行时,会调用此方法

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

// 前置通知(方法执行前)

System.out.println("开始执行" + method.getName() + "方法,参数:" + Arrays.toString(args));

// 调用目标对象的方法

Object result = method.invoke(target, args);

// 后置通知(方法执行后)

System.out.println(method.getName() + "方法执行结束");

return result;

}

}

// 4. 测试JDK动态代理

public class TestProxy {

public static void main(String[] args) {

// 目标对象

UserService target = new UserServiceImpl();

// 创建代理对象(通过JDK Proxy)

UserService proxy = (UserService) Proxy.newProxyInstance(

target.getClass().getClassLoader(),

target.getClass().getInterfaces(),

new LogHandler(target)

);

// 调用代理对象的方法(会触发切面逻辑)

proxy.register(new User("test"));

// 输出:

// 开始执行register方法,参数:[User{username='test'}]

// 执行注册业务:User{username='test'}

// register方法执行结束

}

}
(2)CGLIB 动态代理(针对类)

当目标对象未实现接口时,Spring 使用 CGLIB(Code Generation Library)生成代理对象:通过继承目标类并重写方法,织入切面逻辑。

示例:CGLIB 代理的核心原理

// 1. 目标对象(未实现接口)

public class OrderService {

public void createOrder() {

System.out.println("创建订单");

}

}

// 2. CGLIB回调(类似InvocationHandler)

public class LogInterceptor implements MethodInterceptor {

@Override

public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

// 前置通知

System.out.println("开始执行" + method.getName() + "方法");

// 调用目标对象的方法(通过CGLIB的MethodProxy)

Object result = proxy.invokeSuper(obj, args);

// 后置通知

System.out.println(method.getName() + "方法执行结束");

return result;

}

}

// 3. 测试CGLIB代理

public class TestCGLIB {

public static void main(String[] args) {

// 创建CGLIB增强器

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(OrderService.class); // 设置父类(目标类)

enhancer.setCallback(new LogInterceptor()); // 设置回调

// 生成代理对象(继承OrderService)

OrderService proxy = (OrderService) enhancer.create();

// 调用代理对象的方法

proxy.createOrder();

// 输出:

// 开始执行createOrder方法

// 创建订单

// createOrder方法执行结束

}

}
(3)Spring AOP 的代理选择逻辑
  • 若目标对象实现接口,默认使用JDK 动态代理
  • 若目标对象未实现接口,使用CGLIB 代理
  • 可通过配置强制使用 CGLIB(@EnableAspectJAutoProxy(proxyTargetClass = true))。

3. Spring AOP 实战:通过注解实现切面

Spring AOP 通过 AspectJ 注解简化切面开发,核心步骤:定义切面→配置切入点→编写通知。

(1)环境准备

导入 AOP 依赖(Maven):


<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-aspects</artifactId>

<version>5.3.20</version>

</dependency>

在配置类中启用 AOP:

@Configuration

@ComponentScan("com.example")

@EnableAspectJAutoProxy // 启用AOP注解支持

public class SpringConfig {

}

(2)定义切面(包含切入点和通知)


// 日志切面(@Aspect标识为切面)

@Aspect

@Component // 需被Spring容器管理

public class LogAspect {

// 切入点:匹配com.example.service包下所有类的所有方法

@Pointcut("execution(* com.example.service..*.*(..))")

public void servicePointcut() {} // 切入点签名(无实际逻辑)

// 前置通知:目标方法执行前执行

@Before("servicePointcut()")

public void before(JoinPoint joinPoint) { // JoinPoint包含方法信息

String methodName = joinPoint.getSignature().getName(); // 获取方法名

Object[] args = joinPoint.getArgs(); // 获取方法参数

System.out.println("【前置通知】" + methodName + "方法开始执行,参数:" + Arrays.toString(args));

}

// 后置通知:目标方法执行后执行(无论是否异常)

@After("servicePointcut()")

public void after(JoinPoint joinPoint) {

String methodName = joinPoint.getSignature().getName();

System.out.println("【后置通知】" + methodName + "方法执行结束");

}

// 返回通知:目标方法正常返回后执行

@AfterReturning(value = "servicePointcut()", returning = "result")

public void afterReturning(JoinPoint joinPoint, Object result) {

String methodName = joinPoint.getSignature().getName();

System.out.println("【返回通知】" + methodName + "方法返回结果:" + result);

}

// 异常通知:目标方法抛出异常后执行

@AfterThrowing(value = "servicePointcut()", throwing = "ex")

public void afterThrowing(JoinPoint joinPoint, Exception ex) {

String methodName = joinPoint.getSignature().getName();

System.out.println("【异常通知】" + methodName + "方法抛出异常:" + ex.getMessage());

}

// 环绕通知:包围目标方法,可自定义执行逻辑

@Around("servicePointcut()")

public Object around(ProceedingJoinPoint pjp) throws Throwable {

String methodName = pjp.getSignature().getName();

long start = System.currentTimeMillis();

// 执行目标方法(必须调用,否则目标方法不执行)

Object result = pjp.proceed();

long end = System.currentTimeMillis();

System.out.println("【环绕通知】" + methodName + "方法执行耗时:" + (end - start) + "ms");

return result;

}

}
(3)目标对象(业务类)
@Service

public class UserService {

public User register(User user) {

System.out.println("执行注册业务:" + user);

return user; // 返回用户对象

}

public void delete(String username) {

System.out.println("执行删除业务:" + username);

// 模拟异常

if ("admin".equals(username)) {

throw new RuntimeException("不允许删除管理员");

}

}

}
(4)测试 AOP 效果
public class TestAOP {

public static void main(String[] args) {

ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

UserService userService = context.getBean(UserService.class);

// 测试正常方法

System.out.println("=====测试register方法=====");

userService.register(new User("test"));

// 测试异常方法

System.out.println("\n=====测试delete方法=====");

try {

userService.delete("admin");

} catch (Exception e) {

// 捕获异常(不影响程序执行)

}

}

}

输出结果

=====测试register方法=====

【前置通知】register方法开始执行,参数:[User{username='test'}]

执行注册业务:User{username='test'}

【返回通知】register方法返回结果:User{username='test'}

【后置通知】register方法执行结束

【环绕通知】register方法执行耗时:1ms

=====测试delete方法=====

【前置通知】delete方法开始执行,参数:[admin]

执行删除业务:admin

【异常通知】delete方法抛出异常:不允许删除管理员

【后置通知】delete方法执行结束

结果分析

  • 通知按@Around→@Before→目标方法→@AfterReturning/@AfterThrowing→@After的顺序执行;
  • 异常通知仅在目标方法抛出异常时执行,返回通知仅在正常返回时执行。
(5)切入点表达式详解

切入点表达式用于匹配目标方法,Spring AOP 支持多种表达式,最常用的是execution():

execution(修饰符 返回值 包名.类名.方法名(参数) 异常)

示例

  • execution(* com.example.service.UserService.register(..)):匹配UserService的register方法(任意参数);
  • execution(* com.example.service..*(..)):匹配com.example.service包及子包下的所有方法;
  • execution(public * *(..)):匹配所有公共方法;
  • execution(* *..*Service.*(..)):匹配类名以Service结尾的所有方法。

4. AOP 的实际应用场景

(1)日志记录

如上述示例,记录方法调用日志(参数、返回值、耗时)。

(2)权限校验

在方法执行前校验用户权限,无权限则抛出异常:

@Aspect

@Component

public class AuthAspect {

@Before("execution(* com.example.service..*.*(..)) && @annotation(RequireAuth)")

public void checkAuth() {

// 模拟权限校验

if (!isLogin()) {

throw new RuntimeException("未登录,无权访问");

}

}

private boolean isLogin() {

// 实际开发中从Session或Token获取登录状态

return false;

}

}

// 自定义权限注解

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

@interface RequireAuth {

}

// 在需要权限的方法上标注

@Service

public class OrderService {

@RequireAuth // 需权限校验

public void pay() {

System.out.println("支付订单");

}

}
(3)异常统一处理

捕获方法抛出的异常,转换为友好提示:

@Aspect

@Component

public class ExceptionAspect {

@AfterThrowing(value = "execution(* com.example.service..*.*(..))", throwing = "ex")

public void handleException(Exception ex) {

// 记录异常日志

System.err.println("捕获异常:" + ex.getMessage());

// 可在此处转换异常类型(如将SQLException转为业务异常)

}

}

二、Spring 事务管理:保证数据一致性

在数据库操作中,事务(Transaction)是一组不可分割的操作单元,要么全部成功,要么全部失败(ACID 特性)。Spring 通过声明式事务管理,简化了传统 JDBC 事务的代码(conn.setAutoCommit(false)、commit()、rollback())。

1. 事务的 ACID 特性

  • 原子性(Atomicity):事务中的操作要么全做,要么全不做(如转账时 “扣款” 和 “加款” 必须同时成功或失败);
  • 一致性(Consistency):事务执行前后,数据状态保持一致(如转账前后总金额不变);
  • 隔离性(Isolation):多个事务并发执行时,彼此不干扰(避免脏读、不可重复读、幻读);
  • 持久性(Durability):事务提交后,数据修改永久保存(即使系统崩溃也不丢失)。

2. 传统 JDBC 事务的问题

手动管理事务需编写大量重复代码,且容易遗漏异常处理:


public void transfer(String from, String to, int amount) {

Connection conn = null;

try {

conn = JDBCUtils.getConnection();

conn.setAutoCommit(false); // 开启事务(关闭自动提交)

// 核心操作:扣款、加款

updateBalance(conn, from, -amount); // 转出账户扣款

updateBalance(conn, to, +amount); // 转入账户加款

conn.commit(); // 提交事务

} catch (SQLException e) {

if (conn != null) try { conn.rollback(); } catch (SQLException ex) {} // 回滚事务

e.printStackTrace();

} finally {

JDBCUtils.close(conn);

}

}

问题:每个事务方法都需编写try-catch、commit、rollback,代码冗余。

3. Spring 声明式事务:通过注解简化管理

Spring 声明式事务基于 AOP 实现:通过@Transactional注解标识事务方法,

容器自动织入事务管理逻辑(开启、提交、回滚),开发者无需手动操作。

4. 声明式事务的实现步骤

(1)环境准备

导入 Spring 事务和 JDBC 依赖:

<!-- Spring事务 -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-tx</artifactId>

<version>5.3.20</version>

</dependency>

<!-- Spring JDBC(用于整合数据库) -->

<dependency>

<groupId>org.springframework</groupId>

<artifactId>spring-jdbc</artifactId>

<version>5.3.20</version>

</dependency>

<!-- 数据库驱动 -->

<dependency>

<groupId>mysql</groupId>

<artifactId>mysql-connector-java</artifactId>

<version>8.0.28</version>

</dependency>
(2)配置数据源和事务管理器
@Configuration

@ComponentScan("com.example")

@EnableTransactionManagement // 启用声明式事务

public class SpringConfig {

// 配置数据源

@Bean

public DataSource dataSource() {

DruidDataSource dataSource = new DruidDataSource();

dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");

dataSource.setUrl("jdbc:mysql://localhost:3306/test");

dataSource.setUsername("root");

dataSource.setPassword("123456");

return dataSource;

}

// 配置JdbcTemplate(简化数据库操作)

@Bean

public JdbcTemplate jdbcTemplate(DataSource dataSource) {

return new JdbcTemplate(dataSource);

}

// 配置事务管理器(核心:负责事务的开启、提交、回滚)

@Bean

public PlatformTransactionManager transactionManager(DataSource dataSource) {

return new DataSourceTransactionManager(dataSource);

}

}
(3)使用 @Transactional 注解标识事务方法
@Service

public class TransferService {

@Autowired

private JdbcTemplate jdbcTemplate;

// 声明式事务:该方法在事务中执行

@Transactional

public void transfer(String from, String to, int amount) {

// 1. 转出账户扣款

jdbcTemplate.update(

"UPDATE account SET balance = balance - ? WHERE username = ?",

amount, from

);

// 模拟异常(测试事务回滚)

// if (true) throw new RuntimeException("转账失败");

// 2. 转入账户加款

jdbcTemplate.update(

"UPDATE account SET balance = balance + ? WHERE username = ?",

amount, to

);

}

}
(4)测试事务效果
public class TestTransaction {

public static void main(String[] args) {

ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

TransferService transferService = context.getBean(TransferService.class);

// 初始化数据(假设账户初始余额均为1000)

JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);

jdbcTemplate.update("INSERT INTO account VALUES ('张三', 1000) ON DUPLICATE KEY UPDATE balance = 1000");

jdbcTemplate.update("INSERT INTO account VALUES ('李四', 1000) ON DUPLICATE KEY UPDATE balance = 1000");

try {

// 执行转账(张三给李四转200)

transferService.transfer("张三", "李四", 200);

System.out.println("转账成功");

} catch (Exception e) {

System.out.println("转账失败:" + e.getMessage());

}

// 查询余额

int zhangsan = jdbcTemplate.queryForObject(

"SELECT balance FROM account WHERE username = '张三'", Integer.class

);

int lisi = jdbcTemplate.queryForObject(

"SELECT balance FROM account WHERE username = '李四'", Integer.class

);

System.out.println("张三余额:" + zhangsan + ",李四余额:" + lisi);

}

}

正常情况(无异常)输出

转账成功

张三余额:800,李四余额:1200

异常情况(取消注释throw new RuntimeException)输出

转账失败:转账失败

张三余额:1000,李四余额:1000

结果分析

  • 无异常时,事务正常提交,扣款和加款操作均生效;
  • 有异常时,事务自动回滚,所有操作均不生效(余额恢复初始值)。

5. 事务属性详解

@Transactional注解支持多种属性,用于控制事务的行为:

(1)传播行为(propagation)

定义事务方法之间调用时的事务关系,常用传播行为:

  • REQUIRED(默认):若当前有事务,则加入该事务;否则创建新事务;
  • REQUIRES_NEW:无论当前是否有事务,都创建新事务(原事务暂停);
  • SUPPORTS:若当前有事务,则加入;否则以非事务方式执行;
  • NOT_SUPPORTED:以非事务方式执行,若当前有事务则暂停;
  • NEVER:以非事务方式执行,若当前有事务则抛出异常;
  • MANDATORY:必须在事务中执行,若当前无事务则抛出异常;
  • NESTED:若当前有事务,则在嵌套事务中执行;否则创建新事务。

示例:REQUIRES_NEW的使用


@Service

public class OrderService {

@Autowired

private PaymentService paymentService;

@Transactional(propagation = Propagation.REQUIRED)

public void createOrder() {

System.out.println("创建订单(开启事务)");

try {

paymentService.pay(); // 调用支付方法(新事务)

} catch (Exception e) {

// 支付失败不影响订单事务提交

System.out.println("支付失败,继续创建订单");

}

}

}

@Service

public class PaymentService {

// 无论createOrder是否有事务,pay都创建新事务

@Transactional(propagation = Propagation.REQUIRES_NEW)

public void pay() {

System.out.println("执行支付(新事务)");

throw new RuntimeException("支付超时"); // 仅回滚pay的事务

}

}
(2)隔离级别(isolation)

控制事务并发时的隔离程度,解决脏读、不可重复读、幻读问题:

  • DEFAULT(默认):使用数据库默认隔离级别(MySQL 默认REPEATABLE_READ);
  • READ_UNCOMMITTED:最低隔离级别,允许读取未提交的数据(可能脏读);
  • READ_COMMITTED:只能读取已提交的数据(避免脏读);
  • REPEATABLE_READ:保证多次读取同一数据结果一致(避免脏读、不可重复读);
  • SERIALIZABLE:最高隔离级别,串行执行事务(避免所有并发问题,性能低)。

示例:设置隔离级别


@Transactional(isolation = Isolation.READ_COMMITTED)

public void queryBalance(String username) {

// 业务逻辑

}
(3)超时时间(timeout)

设置事务的最大执行时间(秒),超过则自动回滚:

@Transactional(timeout = 5) // 超时时间5秒

public void longTimeTask() {

// 若任务执行超过5秒,事务回滚

}
(4)只读(readOnly)

标识事务是否为只读(仅查询操作),数据库可优化性能:

@Transactional(readOnly = true) // 只读事务

public List<User> queryAll() {

return userDAO.findAll();

}
(5)回滚策略(rollbackFor/noRollbackFor)

指定哪些异常触发回滚或不回滚(默认:运行时异常回滚,受检异常不回滚):


// 仅当抛出RuntimeException或SQLException时回滚

@Transactional(rollbackFor = {RuntimeException.class, SQLException.class})

public void updateData() {

// 业务逻辑

}

// 当抛出BusinessException时不回滚

@Transactional(noRollbackFor = BusinessException.class)

public void process() {

// 业务逻辑

}

6. 事务的底层原理:AOP 的应用

Spring 事务管理基于 AOP 实现,核心是TransactionInterceptor(事务拦截器):

当调用@Transactional标注的方法时,AOP 生成代理对象;

代理对象先通过TransactionManager开启事务;

调用目标方法执行业务逻辑;

若目标方法正常返回,提交事务;

若目标方法抛出异常(符合回滚策略),回滚事务。

伪代码:事务拦截器的核心逻辑

public class TransactionInterceptor {

private PlatformTransactionManager txManager;

public Object invoke(MethodInvocation invocation) throws Throwable {

// 1. 获取事务属性(@Transactional的配置)

TransactionAttribute txAttr = getTransactionAttribute(invocation);

// 2. 开启事务

TransactionStatus status = txManager.getTransaction(txAttr);

try {

// 3. 调用目标方法

Object result = invocation.proceed();

// 4. 提交事务

txManager.commit(status);

return result;

} catch (Exception e) {

// 5. 符合回滚条件则回滚

if (txAttr.rollbackOn(e)) {

txManager.rollback(status);

} else {

txManager.commit(status); // 否则提交

}

throw e;

}

}

}

三、项目实战:整合 AOP 与事务

实现一个用户注册功能,包含:

  • AOP 日志记录注册过程;
  • 事务保证注册和日志表操作的一致性。

1. 数据库表设计

-- 用户表

CREATE TABLE user (

id INT PRIMARY KEY AUTO_INCREMENT,

username VARCHAR(50) UNIQUE NOT NULL,

password VARCHAR(50) NOT NULL

);

-- 操作日志表

CREATE TABLE operation_log (

id INT PRIMARY KEY AUTO_INCREMENT,

operation VARCHAR(100) NOT NULL,

create_time DATETIME NOT NULL

);

2. 代码实现

(1)实体类

public class User {

private Integer id;

private String username;

private String password;

// 省略getter、setter

}
(2)DAO 层
@Repository

public class UserDAO {

@Autowired

private JdbcTemplate jdbcTemplate;

public void save(User user) {

jdbcTemplate.update(

"INSERT INTO user (username, password) VALUES (?, ?)",

user.getUsername(), user.getPassword()

);

}

}

@Repository

public class LogDAO {

@Autowired

private JdbcTemplate jdbcTemplate;

public void saveLog(String operation) {

jdbcTemplate.update(

"INSERT INTO operation_log (operation, create_time) VALUES (?, NOW())",

operation

);

}

}
(3)Service 层(含事务)
@Service

public class UserService {

@Autowired

private UserDAO userDAO;

@Autowired

private LogDAO logDAO;

// 事务保证:用户注册和日志保存要么同时成功,要么同时失败

@Transactional

public void register(User user) {

// 1. 保存用户

userDAO.save(user);

// 2. 记录日志

logDAO.saveLog("用户注册:" + user.getUsername());

// 模拟异常(测试事务回滚)

// if (true) throw new RuntimeException("注册失败");

}

}
(4)AOP 日志切面
@Aspect

@Component

public class RegisterLogAspect {

@Before("execution(* com.example.service.UserService.register(..)) && args(user)")

public void beforeRegister(User user) {

System.out.println("开始注册用户:" + user.getUsername());

}

@AfterReturning("execution(* com.example.service.UserService.register(..)) && args(user)")

public void afterRegister(User user) {

System.out.println("用户注册成功:" + user.getUsername());

}

}

(5)测试类
public class TestProject {

public static void main(String[] args) {

ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);

UserService userService = context.getBean(UserService.class);

JdbcTemplate jdbcTemplate = context.getBean(JdbcTemplate.class);

// 清理测试数据

jdbcTemplate.update("DELETE FROM user WHERE username = 'test'");

jdbcTemplate.update("DELETE FROM operation_log WHERE operation LIKE '用户注册:test'");

// 执行注册

User user = new User();

user.setUsername("test");

user.setPassword("123456");

try {

userService.register(user);

} catch (Exception e) {

e.printStackTrace();

}

// 验证结果

int userCount = jdbcTemplate.queryForObject(

"SELECT COUNT(*) FROM user WHERE username = 'test'", Integer.class

);

int logCount = jdbcTemplate.queryForObject(

"SELECT COUNT(*) FROM operation_log WHERE operation = '用户注册:test'", Integer.class

);

System.out.println("用户表记录数:" + userCount); // 1(成功)或0(失败回滚)

System.out.println("日志表记录数:" + logCount); // 1(成功)或0(失败回滚)

}

}

正常情况输出

开始注册用户:test

用户注册成功:test

用户表记录数:1

日志表记录数:1

异常情况输出

开始注册用户:test

java.lang.RuntimeException: 注册失败

...

用户表记录数:0

日志表记录数:0

四、总结:AOP 与事务的核心价值

  • Spring AOP:通过横向抽取实现跨模块功能的复用,解决代码冗余问题,核心是动态代理技术。实际开发中常用于日志、权限、异常处理等场景。
  • Spring 事务:基于 AOP 实现声明式事务管理,通过@Transactional注解简化事务操作,保证数据库操作的 ACID 特性,核心是事务管理器和传播行为。

两者结合能极大提升代码质量:AOP 让业务代码更纯净,事务保证数据一致性。理解其底层原理(动态代理、AOP 织入),能帮助更好地排查问题(如事务不生效的原因),写出更健壮的企业级应用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

peachcobbler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值