@Async使用

说明:Spring Boot自带的 @Async 注解,加在方法上,可以异步执行。当你需要做异步操作时,与其引入庞大的MQ组件,不妨尝试用这个注解。本文介绍如何使用 @Async 注解,及使用上的一些注意事项。

搭建Demo

首先,搭建一个Demo,pom.xml文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.12</version>
        <relativePath/>
    </parent>

    <groupId>com.hezy</groupId>
    <artifactId>async_demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.8</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

</project>

实体类,User

import lombok.Data;

import java.io.Serializable;

@Data
public class User implements Serializable {

    private String id;

    private String username;

    private String password;

    private String test;
}

DTO,用于接收参数和返回数据

import lombok.Data;

import java.io.Serializable;

@Data
public class UserDTO implements Serializable {

    private String id;

    private String username;

    private String password;
}

AsyncController,用于演示

import com.hezy.pojo.UserDTO;
import com.hezy.service.UserServiceImpl;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("async")
@Log4j2
public class AsyncController {

    @Autowired
    private UserServiceImpl userService;

    @PostMapping("test1")
    public List<UserDTO> testAsync1(@RequestBody UserDTO userDTO) {
        log.info("进入接口");
        return userService.insert1(userDTO);
    }
}

UserServiceImpl,先插入,插入后查询

import com.hezy.mapper.UserMapper;
import com.hezy.pojo.UserDTO;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Log4j2
public class UserServiceImpl {

    @Autowired
    private AsyncServiceImpl asyncService;

    @Autowired
    private UserMapper userMapper;

    @Transactional(rollbackFor = Exception.class)
    public List<UserDTO> insert1(UserDTO userDTO) {
        log.info("进入service");
        asyncService.insertDTO1(userDTO);
        return userMapper.selectAll();
    }
}

AsyncServiceImpl,异步操作实现类

import com.hezy.mapper.UserMapper;
import com.hezy.pojo.UserDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncServiceImpl {

    @Autowired
    private UserMapper userMapper;

    @Async
    public void insertDTO1(UserDTO userDTO) {
        userMapper.insertUser(userDTO);
    }
}

UserMapper,数据库操作

import com.hezy.pojo.UserDTO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface UserMapper {

    @Insert("insert into i_users (id, username, password) values(#{user.id}, #{user.username}, #{user.password})")
    void insertUser(@Param("user") UserDTO user);

    @Select("select * from i_users")
    List<UserDTO> selectAll();
}

启动类,需要加 @EnableAsync 注解

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@EnableAsync
@SpringBootApplication
@MapperScan("com.hezy.mapper")
public class Start {
    public static void main(String[] args) {
        SpringApplication.run(Start.class, args);
    }
}

测试

为了效果明显,AsyncServiceImpl 里面的insertDTO1方法,使线程休眠5秒钟,如下:

    @Async
    public void insertDTO1(UserDTO userDTO) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        userMapper.insertUser(userDTO);
    }

接下来,调用接口,新增一条记录。新增前,数据库如下:

在这里插入图片描述

可以看到调用接口后,马上返回结果了。

在这里插入图片描述

数据库添加成功

在这里插入图片描述

这里隐含一个问题:需要考虑到异步操作的后续查询,可能会因为异步操作未完成而未能查询到预期的数据。把线程休眠代码去掉,还是未能查询出此次新增的记录,但不使用 @Async 注解,同步操作,插入后的查询是能查出完整的数据库记录的,如下:

(同步操作)

    // @Async
    public void insertDTO1(UserDTO userDTO) {
        userMapper.insertUser(userDTO);
    }

(能查出完整的数据库记录)

在这里插入图片描述

另外,需要注意:

  • @Async 只能在被Spring管理的Bean内生效,即类被打上@Componet或@Service注解

  • 不能在本类中生效,被调用的异步方法不能是本类的成员方法;

更进一步

接着,再来讨论使用 @Async 异步操作的事务问题,如下,在异步操作里手动制造一个异常

(AsyncController,新增一个接口)

    @PostMapping("test2")
    public List<UserDTO> testAsync2(@RequestBody UserDTO userDTO) {
        log.info("进入接口");
        return userService.insert2(userDTO);
    }

(UserServiceImpl)

    @Transactional(rollbackFor = Exception.class)
    public List<UserDTO> insert2(UserDTO userDTO) {
        log.info("进入service");
        asyncService.insertDTO2(userDTO);
        return userMapper.selectAll();
    }

(在异步操作里手动制造一个异常)

    @Async
    public void insertDTO2(UserDTO userDTO) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        userMapper.insertUser(userDTO);

        // 手动制造一个异常,测试事务
        int i = 1 / 0;
    }

调用接口,触发异常,接口没有返回异常(思考:或许这是个启发,如果你有段逻辑,调用了某个方法,为了避免此方法内抛出了异常而阻塞主逻辑,就可以将此方法改为异步操作。当然,也需要业务上允许,如果主逻辑需要此方法的返回结果,就不能考虑异步)

在这里插入图片描述

延迟5秒后,控制台抛出了异常

在这里插入图片描述

事务没有控制住,数据插入成功。不加 @Async 注解,这种情况事务是能控制住的。

在这里插入图片描述

所以,要在异步操作内控制事务,需要在异步方法上再加上一个事务注解,如下:

    @Transactional(rollbackFor = Exception.class)
    @Async
    public void insertDTO2(UserDTO userDTO) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        userMapper.insertUser(userDTO);

        // 手动制造一个异常,测试事务
        int i = 1 / 0;
    }

重试,添加一条记录

在这里插入图片描述

休眠5秒后,代码执行,控制台抛出异常

在这里插入图片描述

刷新数据库,没有新增成功,事务生效了。

在这里插入图片描述

总结

本文介绍了在Spring Boot中 @Async 的使用,使用 @Async 时,总结以下几点:

  • 启动类需要加 @EnableAsync 注解;

  • 只能在被Spring管理的Bean内生效,即类被打上 @Componet 或 @Service 注解

  • 不能在本类中生效,调用的异步方法不能是本类的成员方法;

  • 异步操作的后续查询,可能会因为异步操作未完成而未能查询到预期的数据;

  • 调用异步操作方法上的声明式事务无法控制异步操作内的事务,如需把控异步操作内的事务,需另外在异步操作方法上加事务注解;

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

何中应

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

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

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

打赏作者

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

抵扣说明:

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

余额充值