@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)
@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 注解可以一起使用,但需要注意一些问题:
- 事务管理失效:由于 @Async 方法是在新线程中执行的,无法访问调用线程中的事务上下文。如果需要在异步方法中执行事务管理,需要重新配置事务。
- 异常处理:异步方法中的异常不会直接传播到调用者,需要通过 Future 或异步任务异常处理机制来处理。
- 事务一致性问题:为了确保事务的一致性,可以采用事件驱动的方式,通过事件发布和监听机制来解耦事务操作和异步任务。
通过这些措施,可以有效地结合 @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
微信公众号
文章同步发布,政采云技术团队公众号,欢迎关注
原文链接: https://juejin.cn/post/7382074040645828620
文章收集整理于网络,请勿商用,仅供个人学习使用,如有侵权,请联系作者删除,如若转载,请注明出处:http://www.cxyroad.com/17122.html