1. 首页
  2. 后端

@Transactional和@Async两个注解能否一起用

  @Transactional和@Async两个注解能否一起用

==============================

![文章顶部.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b637793da67b4e068460a99d94b333ed~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=4722&h=696&s=276733&e=png&b=fafcff)

蓝浅.png

@Transactional和@Async两个注解能否一起用

在 Java 的 Spring 框架中,@Transactional 和@Async 是两个非常常用的注解。前者用于声明事务管理,后者用于异步方法调用。那么,这两个注解能否一起使用呢?本文将详细探讨这个问题,包括它们的工作原理、一起使用时的正确场景和可能出现的问题。

一、@Transactional 注解

@Transactional 注解用于管理事务,确保一组数据库操作要么全部成功,要么全部失败,从而保证数据的一致性。它可以应用于类或方法上。

1. @Transactional 的基本用法

@Transactional 注解可以应用于方法或类上。当应用于类上时,类中所有公共方法都将被默认应用事务管理。

@Service
public class UserService {
​
    @Autowired
    private UserRepository userRepository;
​
    @Transactional
    public void registerUser(User user) {
        userRepository.save(user);
        // 其他业务逻辑
    }
}

在这个示例中,registerUser 方法被 @Transactional 注解标记,表示这个方法中的所有数据库操作将在同一个事务中执行。如果方法执行期间发生任何异常,所有的数据库操作都会回滚。

2. 事务传播行为

@Transactional注解提供了多个传播行为选项,如 REQUIRED、REQUIRES_NEW 等,控制事务的传播方式。

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void someMethod() {
    // 新的事务上下文
}
  • Propagation.REQUIRED:如果当前没有事务,则新建一个事务。如果已经有一个事务,加入到这个事务中。这是默认值。
  • Propagation.REQUIRES_NEW:新建一个事务,如果当前已经有一个事务,暂停当前事务。
  • Propagation.NESTED:如果当前已经有一个事务,则在嵌套事务内执行。如果当前没有事务,则新建一个事务。

3. 事务隔离级别

@Transactional 注解还可以设置事务的隔离级别:

@Transactional(isolation = Isolation.READ_COMMITTED)
public void someMethod() {
    // 事务代码
}
  • Isolation.DEFAULT:使用数据库默认的隔离级别。
  • Isolation.READ_COMMITTED:只能读取已经提交的数据。
  • Isolation.READ_UNCOMMITTED:可以读取未提交的数据。
  • Isolation.REPEATABLE_READ:确保在同一个事务中多次读取同样的数据时,这些数据是一致的。
  • Isolation.SERIALIZABLE:所有事务顺序执行,最严格的隔离级别。

4. 事务超时和回滚规则

@Transactional 注解还允许设置事务的超时和回滚规则。

@Transactional(timeout = 5, rollbackFor = Exception.class)
public void someMethod() {
    // 事务代码
}
  • timeout:事务的超时时间,单位为秒。
  • rollbackFor:指定哪些异常会触发事务回滚。

二、@Async 注解

@Async 注解用于实现异步方法调用,使得方法在单独的线程中执行,提升应用的响应速度。

1. @Async的基本用法

@Service
public class NotificationService {
​
    @Async
    public void sendNotification(String message) {
        // 发送通知的逻辑
    }
}

在这个示例中,sendNotification 方法被 @Async 注解标记,表示这个方法将在一个新的线程中异步执行。

2. 配置异步支持

要使用 @Async 注解,需要在配置类中开启异步支持:

@Configuration
@EnableAsync
public class AsyncConfig {
    // 配置相关代码
}

3. 异步方法返回类型

异步方法可以返回void、Future 或 CompletableFuture。

@Async
public CompletableFuture<String> sendNotification(String message) {
    // 发送通知的逻辑
    return CompletableFuture.completedFuture("Notification sent: " + message);
}

返回 CompletableFuture 使得调用方可以在需要时获取异步结果。

4. 异步异常处理

可以通过自定义 AsyncUncaughtExceptionHandler 来处理异步方法中的未捕获异常。

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {
​
    @Override
    public Executor getAsyncExecutor() {
        return new SimpleAsyncTaskExecutor();
    }
​
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new CustomAsyncExceptionHandler();
    }
}
​
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
​
    @Override
    public void handleUncaughtException(Throwable ex, Method method, Object... params) {
        // 异常处理逻辑
        System.err.println("Async error in method: " + method.getName());
    }
}

三、@Transactional 和 @Async 一起使用

@Transactional 和 @Async 可以一起使用,但需要注意一些问题。

1. 正确的使用场景

业务场景:用户注册和发送通知

假设有一个用户注册的业务场景,用户注册完成后需要发送通知给用户。

@Service
public class UserService {
​
    @Autowired
    private UserRepository userRepository;
​
    @Autowired
    private NotificationService notificationService;
​
    @Transactional
    public void registerUser(User user) {
        userRepository.save(user);
        notificationService.sendNotification("Welcome " + user.getName());
    }
}
​
@Service
public class NotificationService {
​
    @Async
    public void sendNotification(String message) {
        // 模拟发送通知的延迟
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Notification sent: " + message);
    }
}

在上述代码中,registerUser 方法中的事务管理不会受到 sendNotification 方法的影响,因为 sendNotification 在一个新的线程中执行。这种场景下,@Transactional 和 @Async 可以一起正常工作。

2. 可能出现的问题

问题1:事务管理失效

在 Spring 中,@Transactional 注解用来管理事务,但它依赖于当前线程的上下文。如果在一个带有@Transactional 注解的方法中调用另一个带有 @Async 注解的方法,由于 @Async 方法会在一个新的线程中执行,因此该方法无法访问调用线程中的事务上下文。这会导致事务管理失效,无法正确回滚或提交事务。

示例代码

@Service
public class UserService {
​
    @Autowired
    private UserRepository userRepository;
​
    @Autowired
    private AsyncService asyncService;
​
    @Transactional
    public void registerUser(User user) {
        userRepository.save(user);
        // 异步调用
        asyncService.sendWelcomeEmail(user);
        // 其他事务操作
    }
}
​
@Service
public class AsyncService {
​
    @Async
    public void sendWelcomeEmail(User user) {
        // 发送欢迎邮件的逻辑
        System.out.println("Sending welcome email to " + user.getEmail());
    }
}

代码输出结果

plaintextSending welcome email to user@example.com

在上述代码中,sendWelcomeEmail方法是异步执行的,无法参与registerUser方法的事务管理。因此,即使registerUser方法中的事务因某种原因回滚,sendWelcomeEmail方法的执行仍然不受影响,可能会导致数据不一致的问题。

问题2:异常处理

在异步方法中抛出的异常不会直接传播到调用线程,这意味着如果异步方法中出现异常,调用方法可能无法感知到,无法进行相应的处理。

示例代码

@Service
public class AsyncService {
​
    @Async
    public CompletableFuture<Void> sendWelcomeEmail(User user) {
        try {
            // 模拟发送邮件的延迟
            Thread.sleep(3000);
            // 模拟异常
            if (user.getEmail().contains("example")) {
                throw new RuntimeException("Invalid email address");
            }
            System.out.println("Sending welcome email to " + user.getEmail());
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}
​
@Service
public class UserService {
​
    @Autowired
    private AsyncService asyncService;
​
    public void registerUser(User user) {
        CompletableFuture<Void> future = asyncService.sendWelcomeEmail(user);
        future.exceptionally(ex -> {
            System.out.println("Failed to send email: " + ex.getMessage());
            return null;
        });
    }
}

代码输出结果

plaintextFailed to send email: Invalid email address

在上述代码中,sendWelcomeEmail方法中抛出的异常被捕获,并通过CompletableFuture返回给调用方法。调用方法使用exceptionally方法处理异常。这种方式虽然可以捕获异常,但需要显式地处理,增加了代码复杂性。

问题3:事务一致性问题

为了确保事务的一致性,可以使用事件驱动的方式。将事务性操作和异步操作解耦,通过事件发布和监听机制来管理异步任务。这种方式可以确保事务提交后再执行异步操作,避免数据不一致的问题。

示例代码

public class UserRegisteredEvent {
    private final User user;
​
    public UserRegisteredEvent(User user) {
        this.user = user;
    }
​
    public User getUser() {
        return user;
    }
}
​
@Service
public class UserService {
​
    @Autowired
    private UserRepository userRepository;
​
    @Autowired
    private ApplicationEventPublisher eventPublisher;
​
    @Transactional
    public void registerUser(User user) {
        userRepository.save(user);
        eventPublisher.publishEvent(new UserRegisteredEvent(user));
    }
}
​
@Component
public class UserRegisteredListener {
​
    @Autowired
    private NotificationService notificationService;
​
    @EventListener
    @Async
    public void handleUserRegistered(UserRegisteredEvent event) {
        User user = event.getUser();
        notificationService.sendNotification("Welcome " + user.getName());
    }
}
​
@Service
public class NotificationService {
​
    @Async
    public CompletableFuture<Void> sendNotification(String message) {
        try {
            // 模拟发送通知的延迟
            Thread.sleep(3000);
            System.out.println("Notification sent: " + message);
            return CompletableFuture.completedFuture(null);
        } catch (InterruptedException e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}

代码输出结果

plaintextNotification sent: Welcome Carolinai

问题解释

在上述代码中,UserService中的事务操作完成后,发布了UserRegisteredEvent事件。UserRegisteredListener异步监听该事件并调用NotificationService的异步方法发送通知。这样可以确保只有在事务成功提交后才会执行异步操作,避免事务不一致的问题。

3. 解决方案

为了确保事务的一致性,可以采用事件驱动的方式,通过事件发布和监听机制来解耦事务操作和异步任务。

@Service
public class UserService {
​
    @Autowired
    private UserRepository userRepository;
​
    @Autowired
    private ApplicationEventPublisher eventPublisher;
​
    @Transactional
    public void registerUser(User user) {
        userRepository.save(user);
        eventPublisher.publishEvent(new UserRegisteredEvent(user));
    }
}
​
@Component
public class UserRegisteredListener {
​
    @Autowired
    private NotificationService notificationService;
​
    @EventListener
    @Async
    public void handleUserRegistered(UserRegisteredEvent event) {
        notificationService.sendNotification("Welcome " + event.getUser().getName());
    }
}
​
public class UserRegisteredEvent {
​
    private final User user;
​
    public UserRegisteredEvent(User user) {
        this.user = user;
    }
​
    public User getUser() {
        return user;
    }
}

在这个示例中,我们使用Spring的事件发布机制,当用户注册成功后发布一个UserRegisteredEvent事件,异步监听器接收到事件后执行异步任务。这种方式确保了事务操作和异步任务的解耦,避免了事务一致性问题。

4. 综合示例

为了更全面地展示 @Transactional 和 @Async 一起使用的实际场景,下面是一个综合示例,包括用户注册、发送欢迎邮件和记录日志等操作。

@Service
public class UserService {
​
    @Autowired
    private UserRepository userRepository;
​
    @Autowired
    private ApplicationEventPublisher eventPublisher;
​
    @Transactional
    public void registerUser(User user) {
        userRepository.save(user);
        eventPublisher.publishEvent(new UserRegisteredEvent(user));
    }
}
​
@Component
public class UserRegisteredListener {
​
    @Autowired
    private NotificationService notificationService;
​
    @Autowired
    private LogService logService;
​
    @EventListener
    @Async
    public void handleUserRegistered(UserRegisteredEvent event) {
        User user = event.getUser();
        notificationService.sendNotification("Welcome " + user.getName());
        logService.log("User registered: " + user.getName());
    }
}
​
public class UserRegisteredEvent {
​
    private final User user;
​
    public UserRegisteredEvent(User user) {
        this.user = user;
    }
​
    public User getUser() {
        return user;
    }
}

NotificationService类

@Service
public class NotificationService {
​
    @Async
    public CompletableFuture<String> sendNotification(String message) {
        try {
            // 模拟发送通知的延迟
            Thread.sleep(3000);
            System.out.println("Notification sent: " + message);
            return CompletableFuture.completedFuture("Notification sent: " + message);
        } catch (InterruptedException e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}

LogService类

@Service
public class LogService {
​
    @Async
    @Transactional
    public CompletableFuture<Void> log(String message) {
        // 假设我们将日志存储在数据库中
        // 这里是简化的示例,实际使用时请替换为实际的日志记录逻辑
        System.out.println("Log entry: " + message);
        return CompletableFuture.completedFuture(null);
    }
}

四、总结

通过以上综合示例,我们可以看到 @Transactional 和 @Async 注解可以一起使用,但需要注意一些问题:

  1. 事务管理失效:由于 @Async 方法是在新线程中执行的,无法访问调用线程中的事务上下文。如果需要在异步方法中执行事务管理,需要重新配置事务。
  2. 异常处理:异步方法中的异常不会直接传播到调用者,需要通过 Future 或异步任务异常处理机制来处理。
  3. 事务一致性问题:为了确保事务的一致性,可以采用事件驱动的方式,通过事件发布和监听机制来解耦事务操作和异步任务。

通过这些措施,可以有效地结合 @Transactional 和 @Async 注解,提升应用的响应速度,同时确保数据的一致性和事务的完整性。

推荐阅读

Kubernetes Informer基本原理

JDK17 与 JDK11 特性差异浅谈

业务分析师眼中的数据中台

政采云大数据权限系统设计和实现

JDK11 与 JDK8 特性差异浅谈

招贤纳士

政采云技术团队(Zero),Base 杭州,一个富有激情和技术匠心精神的成长型团队。规模 500 人左右,在日常业务开发之外,还分别在云原生、区块链、人工智能、低代码平台、中间件、大数据、物料体系、工程平台、性能体验、可视化等领域进行技术探索和实践,推动并落地了一系列的内部技术产品,持续探索技术的新边界。此外,团队还纷纷投身社区建设,目前已经是 google flutter、scikit-learn、Apache Dubbo、Apache Rocketmq、Apache Pulsar、CNCF Dapr、Apache DolphinScheduler、alibaba Seata 等众多优秀开源社区的贡献者。

如果你想改变一直被事折腾,希望开始折腾事;如果你想改变一直被告诫需要多些想法,却无从破局;如果你想改变你有能力去做成那个结果,却不需要你;如果你想改变你想做成的事需要一个团队去支撑,但没你带人的位置;如果你想改变本来悟性不错,但总是有那一层窗户纸的模糊……如果你相信相信的力量,相信平凡人能成就非凡事,相信能遇到更好的自己。如果你希望参与到随着业务腾飞的过程,亲手推动一个有着深入的业务理解、完善的技术体系、技术创造价值、影响力外溢的技术团队的成长过程,我觉得我们该聊聊。任何时间,等着你写点什么,发给 zcy-tc@cai-inc.com

微信公众号

文章同步发布,政采云技术团队公众号,欢迎关注 文章顶部.png

原文链接: https://juejin.cn/post/7382074040645828620

文章收集整理于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除,如若转载,请注明出处:http://www.cxyroad.com/17122.html

QR code