@Transactional(rollbackFor = {Exception.class}) 和 @Async(“executor“)

时间: 2024-11-10 admin IT培训

@Transactional(rollbackFor = {Exception.class}) 和 @Async(“executor“)

@Transactional(rollbackFor = {Exception.class}) 和 @Async(“executor“)

1.@Transactional(rollbackFor = {Exception.class})

必须在MybatisConfig配置类中开启事务注解@EnableTransactionManagement//开启事务管理,才能使@Transactional

当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
在项目中,@Transactional(rollbackFor=Exception.class),如果类加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。在@Transactional注解中如果不配置rollbackFor属性,那么事物只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事物在遇到非运行时异常时也回滚

注意: 如果异常被try{…}catch{…}了,事务就不回滚了,如果想让事务回滚必须再往外抛try{…}catch{throw new xxException},并在方法名处throws 抛出异常

五种运行时异常:
ClassCastException(类转换异常)
IndexOutOfBoundsException(数组越界)
NullPointerException(空指针)
ArrayStoreException(数据存储异常,操作数组时类型不一致)
还有IO操作的BufferOverflowException异常

常见的非运行异常有io异常和sql异常
IOException
FileNotFoundExcetion
SQLException

2.@Async(“executor”)

摘自:SpringBoot如何实现异步调用?
(1)为什么要异步处理?
第一个原因:容错性、健壮性,如果送积分出现异常,不能因为送积分而导致用户注册失败;因为用户注册是主要功能,送积分是次要功能,即使送积分异常也要提示用户注册成功,然后后面在针对积分异常做补偿处理。
第二个原因:提升性能,例如注册用户花了20毫秒,送积分花费50毫秒,如果用同步的话,总耗时70毫秒,用异步的话,无需等待积分,故耗时20毫秒。

(2)使用步骤
Spring3开始就提供了@Async注解,我们只需要在方法上标注此注解,此方法即可实现异步调用,还需要一个配置类,通过Enable模块驱动注解@EnableAsync来开启异步功能,@EnableAsync注解可以直接放在SpringBoot启动类上,也可以单独放在其他配置类上。我们这里选择使用单独的配置类AsyncConfiguration,多个并发接口时,根据接口特征定义多个线程池,这时候我们在使用@Async时就需要通过指定线程池名称进行区分

(3)异步执行的好处:主线程的执行不需要等待异步方法执行完成

1.配置类,配置线程池参数

import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;/*** 描述:异步配置*/
@Slf4j
@Configuration
@EnableAsync    // 可放在启动类上或单独的配置类
public class AsyncConfiguration implements AsyncConfigurer {@Bean(name = "executor")public ThreadPoolTaskExecutor executor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();//核心线程数taskExecutor.setCorePoolSize(10);//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程taskExecutor.setMaxPoolSize(100);//缓存队列taskExecutor.setQueueCapacity(50);//设置线程的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁taskExecutor.setKeepAliveSeconds(200);//异步方法内部线程名称taskExecutor.setThreadNamePrefix("async-");/*** 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略* 通常有以下四种策略:* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)* ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功*/taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());taskExecutor.initialize();return taskExecutor;}/*** 指定默认线程池* The {@link Executor} instance to be used when processing async method invocations.*/@Overridepublic Executor getAsyncExecutor() {return executor();}/*** The {@link AsyncUncaughtExceptionHandler} instance to be used* when an exception is thrown during an asynchronous method execution* with {@code void} return type.*/@Overridepublic AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {return (ex, method, params) -> log.error("线程池执行任务发送未知错误, 执行方法:{}", method.getName(), ex);}
}

2.标记异步方法,标记的异步方法只能外部调用才能生效

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;/*** 描述:异步方法调用*/
@Slf4j
@Component
public class AsyncTask {@SneakyThrows@Async  // 在异步配置类设置了默认线程池则不需要再指定线程池名称
//    @Async("asyncPoolTaskExecutor")public void doTask1() {long t1 = System.currentTimeMillis();Thread.sleep(2000);long t2 = System.currentTimeMillis();log.info("task1方法耗时 {} ms" , t2-t1);}@SneakyThrows@Async
//    @Async("otherPoolTaskExecutor") // 其他线程池的名称public void doTask2() {long t1 = System.currentTimeMillis();Thread.sleep(3000);long t2 = System.currentTimeMillis();log.info("task2方法耗时 {} ms" , t2-t1);}
}

3.Controller层进行异步调用

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@Slf4j
@RestController
@RequestMapping("/async")
public class AsyncController {@Autowiredprivate AsyncTask asyncTask;@RequestMapping("/task")public void task() throws InterruptedException {long t1 = System.currentTimeMillis();asyncTask.doTask1();asyncTask.doTask2();Thread.sleep(1000);long t2 = System.currentTimeMillis();log.info("main方法耗时{} ms", t2-t1);}
}

重要:为什么要给@Async自定义线程池?
使用@Async注解,在默认情况下用的是SimpleAsyncTaskExecutor线程池,该线程池不是真正意义上的线程池。
使用此线程池无法实现线程重用,每次调用都会新建一条线程。若系统中不断的创建线程,最终会导致系统占用内存过高,引发OutOfMemoryError错误,所以我们在使用Spring中的@Async异步框架时一定要自定义线程池,替代默认的SimpleAsyncTaskExecutor