You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用@Async注解的方法返回空列表,原因何在?

问题:@Async注解导致Spring Data JPA仓库返回空列表

问题背景

在Spring Data JPA仓库方法上使用@Async注解后,调用方法返回空列表;移除启动类上的@EnableAsync注解后,查询能正常返回预期的3条数据。以下是完整的最小复现示例:

异步仓库接口

public interface AsynchronousCardRepository extends Repository<Card, UUID> {
    @Async
    Future<List<Card>> findAll();
}

测试类

@DataJpaTest
@ActiveProfiles("test")
@Sql(
        executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
        scripts = {"/schema-h2.sql", "/data-h2.sql"}
)
public class AsynchronousCardRepositoryTest {
    @Autowired
    AsynchronousCardRepository cardRepository;

    @Test
    @SneakyThrows
    void findAll() {
        Future<List<Card>> futureCards = cardRepository.findAll();
        List<Card> cards = futureCards.get();
        assertThat(cards).asList().hasSize(3);
    }
}

数据库表结构SQL

-- schema

CREATE TABLE IF NOT EXISTS PUBLIC.account
(
    id                        UUID           PRIMARY KEY
);
CREATE TABLE IF NOT EXISTS PUBLIC.card
(
    id                UUID             PRIMARY KEY,
    account_id        UUID             NOT NULL REFERENCES account (id),
    status            VARCHAR(30)      NOT NULL
);

测试数据SQL

-- data

INSERT INTO PUBLIC.ACCOUNT
VALUES ('00000000-0000-0000-0000-000000000ac1');

INSERT INTO PUBLIC.CARD
VALUES ('00000000-0000-0000-0000-0000000000c1', '00000000-0000-0000-0000-000000000ac1', 'ACTIVE');

INSERT INTO PUBLIC.CARD
VALUES ('00000000-0000-0000-0000-0000000000c2', '00000000-0000-0000-0000-000000000ac1', 'ACTIVE');

INSERT INTO PUBLIC.CARD
VALUES ('00000000-0000-0000-0000-0000000000c3', '00000000-0000-0000-0000-000000000ac1', 'NON_ACTIVE');

Card实体类

@Entity
@Table(name = "card")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Card {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID id;
    @ManyToOne
    @JoinColumn(name = "account_id", referencedColumnName = "id")
    private Account account;
    @Enumerated(value = EnumType.STRING)
    private CardStatus status;
}

CardStatus枚举

public enum CardStatus {
    ACTIVE, NON_ACTIVE
}

Account实体类

@Entity
@Table(name = "account")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private UUID id;
    @OneToMany(mappedBy = "account")
    private List<Card> cards;
}

Spring启动类

@SpringBootApplication
@EnableAsync // 移除该注解后查询正常
public class CreditServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(CreditServiceApplication.class, args);
    }
}

测试环境配置

spring:
  datasource:
    url: jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;MODE=MySQL;DATABASE_TO_LOWER=TRUE
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: none
    show-sql: true
    database-platform: org.hibernate.dialect.H2Dialect
    properties:
      hibernate:
        format_sql: true
  h2:
    console:
      enabled: false
      settings:
        web-allow-others: true
  config:
    activate:
      on-profile: test

logging:
  level:
    org:
      hibernate:
        type: TRACE
      springframework:
          jdbc:
            core: TRACE
            datasource:
              init: DEBUG
          test:
            context:
              jdbc: DEBUG

报错信息

java.lang.AssertionError: 
Expected size: 3 but was: 0 in:
[]

    at by.afinny.credit.unit.repository.AsynchronousCardRepositoryTest.findAll(AsynchronousCardRepositoryTest.java:32)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)

原因分析

核心问题是事务上下文的隔离:

  1. @DataJpaTest会为每个测试方法自动创建事务,默认测试结束后回滚该事务。
  2. @Sql脚本的插入操作在测试线程的事务中执行,此时数据未提交到数据库(事务未结束)。
  3. @Async注解会让方法在全新线程中执行,该线程无法继承测试线程的事务上下文。异步线程执行查询时,测试线程的事务仍处于未提交状态,因此看不到@Sql插入的数据,返回空列表。
  4. 移除@EnableAsync后,方法同步执行,和@Sql脚本处于同一事务上下文,能读取到未提交的数据,因此返回正确结果。

解决方案

方案一:修改测试类的事务传播行为

给测试方法添加@Transactional(propagation = Propagation.NOT_SUPPORTED),让测试在非事务环境下执行,@Sql的插入操作会自动提交到数据库,异步线程即可查到数据:

@Test
@SneakyThrows
@Transactional(propagation = Propagation.NOT_SUPPORTED)
void findAll() {
    Future<List<Card>> futureCards = cardRepository.findAll();
    List<Card> cards = futureCards.get();
    assertThat(cards).asList().hasSize(3);
}

方案二:给异步仓库方法添加独立事务

在仓库方法上添加@Transactional(propagation = Propagation.REQUIRES_NEW),确保异步方法在独立事务中执行,同时给测试类添加@Rollback(false)让测试事务提交:

public interface AsynchronousCardRepository extends Repository<Card, UUID> {
    @Async
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    Future<List<Card>> findAll();
}

测试类修改:

@DataJpaTest
@ActiveProfiles("test")
@Sql(
        executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD,
        scripts = {"/schema-h2.sql", "/data-h2.sql"}
)
@Rollback(false)
public class AsynchronousCardRepositoryTest {
    // ... 测试代码
}

方案三:手动控制测试事务提交

在测试方法中注入TestTransaction,手动提交事务后再调用异步方法:

@Autowired
private TestTransaction testTransaction;

@Test
@SneakyThrows
void findAll() {
    // 提交测试事务,让@Sql的数据写入数据库
    testTransaction.end();
    testTransaction.start();
    
    Future<List<Card>> futureCards = cardRepository.findAll();
    List<Card> cards = futureCards.get();
    assertThat(cards).asList().hasSize(3);
}

内容的提问来源于stack exchange,提问作者Sergey Zolotarev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 08:58:11