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

Spring Data Repository集成测试本地通过Jenkins失败求助

问题:Spring Data saveAll()在本地和Jenkins环境行为不一致

我正在修复验证Spring Data Repository的saveAll()和findAll()方法行为的旧集成测试,这些测试本地运行正常,但部署到Jenkins后总是失败。已定位差异源于saveAll()方法行为不同:本地执行插入3条测试数据,Jenkins环境仅插入2条并执行1条更新,导致断言(预期3条实际2条)失败。以下是相关代码与配置信息:

application-test.yaml

Spring:
  jpa:
    spring:
      jpa:
        database-platform: org.hibernate.dialect.H2Dialect
  liquibase:
    enabled: true
  datasource:
    initialization-mode: always
    url: "jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE"
    driver-class-name: org.h2.Driver
    username: sa
    password: password

实体类代码

Fields.java

public class Fields implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="ID", updatable = false, nullable = false, unique = true, columnDefinition = "INTEGER")
    @NotNull
    private Long id;

    @NotNull
    @Column(name="PRODUCT_ID", columnDefinition = "INTEGER")
    private Long personId;

}

Family.java

public class Family extends Fields {

    public Family(@NotNull Long id, @NotNull Long personId, String group, String name, String value, LocalDateTime lastChangeDate) {
        super(id, personId);
        this.group = group;
        this.name = name;
        this.value = value;
        this.lastChangeTsDate = lastChangeTsDate;
    }

    @NotNull
    @Column(name="GROUP")
    private String group;

    @NotNull
    @Column(name="NAME")
    private String name;

    @NotNull
    @Column(name="VALUE")
    private String value;

    @NotNull
    @CreationTimestamp
    @Column(name = "LAST_CHANGE_DATE")
    private LocalDateTime lastChangeDate;
}

Repository代码

@Repository
public interface FamilyRepository extends CrudRepository<Family, Long> {
}

测试类代码

@DataJpaTest
@ActiveProfiles("test")
@ExtendWith(MockitoExtension.class)
public class FamilyRepositoryTestInt {
    @Autowired
    private FamilyRepository familyRepository;

    @Test
    public void testFindAll() {
        familyRepository.deleteAll();
        
        long total = familyRepository.count();
        assertEquals(0, total);

        LocalDateTime timeNow = LocalDateTime.now();

        Family family1 = new Family(1L, 1L, "group 1", "param name 1", "param value 1", LocalDateTime.now());
        Family family2 = new Family(2L, 2L, "group 2", "param name 2", "param value 2", LocalDateTime.now());
        Family family3 = new Family(3L, 3L, "group 3", "param name 3", "param value 3", LocalDateTime.now());

        Iterable<Family> savefamilys = familyRepository.saveAll(List.of(family1, family2, family3));
        savefamilys.forEach(pp -> System.out.println("Saved: " + pp.getId()));

        Iterable<Family> findAllfamilys = familyRepository.findAll();
        findAllfamilys.forEach(pp -> System.out.println("Found: " + pp.getId()));

        assertEquals(savefamilys, findAllfamilys);
    }
}

本地执行日志(3条插入)

Hibernate: select familyfamily0_.id as id1_5_, p
Hibernate: select count(*) as col_0_0_ from pr
Hibernate: select familyfamily0_.id as id1_5_0_,
Hibernate: insert into family_familyam (id, prod
Hibernate: select familyfamily0_.id as id1_5_0_,
Hibernate: insert into family_familyam (id, prod
Hibernate: select familyfamily0_.id as id1_5_0_,
Hibernate: insert into family_familyam (id, prod
Saved: 1
Saved: 2
Saved: 3
Hibernate: select familyfamily0_.id as id1_5_, 
Found: 1
Found: 2
Found: 3
Hibernate: select count(*) as col_0_0_ from 
Hibernate: select familyfamily0_.id as id1_5_,

Jenkins执行日志(2条插入+1条更新)

Hibernate: select family0_.id as id1_5_
Hibernate: select count(*) as col_0_0_ from
Hibernate: select family0_.id as id1_5_
Hibernate: insert into family (id, p
Hibernate: select family0_.id as id1_5_
Hibernate: insert into family (id, p
Saved: 2
Saved: 2
Saved: 3
Hibernate: update family set person_id=?, 
Hibernate: select family0_.id as id1_5_,
Found: 2
Found: 3

问题分析与解决方案

核心原因

1. ID生成策略与手动赋值冲突

实体Fields的ID使用@GeneratedValue(strategy = GenerationType.IDENTITY),即由数据库自增生成ID,但测试代码中手动给Family实例指定了ID值(如new Family(1L, ...))。

  • 本地H2在MySQL模式下允许手动插入自增列的值,因此三条数据ID分别为1、2、3,全部执行插入。
  • Jenkins环境的H2版本/配置不同,在MySQL模式下会忽略手动指定的自增ID,自动生成下一个递增ID:
    • family1手动指定ID=1,数据库自动生成ID=2;
    • family2手动指定ID=2,Hibernate检测到该ID已存在,执行更新覆盖family1的数据;
    • family3手动指定ID=3,数据库自动生成ID=3,执行插入。
      最终数据库仅存2条数据。

2. 配置文件嵌套错误

application-test.yaml中JPA配置存在无效嵌套:

Spring:
  jpa:
    spring:
      jpa:
        database-platform: org.hibernate.dialect.H2Dialect

重复的spring.jpa嵌套导致部分配置未生效,加剧了环境差异。

3. 断言逻辑错误

assertEquals(savefamilys, findAllfamilys)使用Iterable默认的引用相等判断,而非集合内容比对,即使数据正确也可能断言失败。

解决方案

1. 统一ID生成逻辑

方案A:依赖数据库自增(推荐)

  • 调整Family构造函数,移除ID参数,让数据库自动生成ID:
    public Family(@NotNull Long personId, String group, String name, String value, LocalDateTime lastChangeDate) {
        super(personId); // 同步调整Fields构造函数,移除ID参数
        this.group = group;
        this.name = name;
        this.value = value;
        this.lastChangeTsDate = lastChangeTsDate;
    }
    
  • 测试代码中不再手动指定ID:
    Family family1 = new Family(1L, "group 1", "param name 1", "param value 1", LocalDateTime.now());
    

方案B:手动指定ID

将ID生成策略改为ASSIGNED,明确使用手动指定的ID:

@Id
@GeneratedValue(strategy = GenerationType.ASSIGNED)
@Column(name="ID", updatable = false, nullable = false, unique = true, columnDefinition = "INTEGER")
@NotNull
private Long id;

注意:需确保手动指定的ID全局唯一。

2. 修复配置文件

修正JPA配置的嵌套错误:

spring:
  jpa:
    database-platform: org.hibernate.dialect.H2Dialect
  liquibase:
    enabled: true
  datasource:
    initialization-mode: always
    url: "jdbc:h2:mem:testdb;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE"
    driver-class-name: org.h2.Driver
    username: sa
    password: password

3. 修正断言逻辑

转换为List后比对元素内容:

List<Family> savedList = StreamSupport.stream(savefamilys.spliterator(), false).toList();
List<Family> foundList = StreamSupport.stream(findAllfamilys.spliterator(), false).toList();

// 先比对数量
assertEquals(savedList.size(), foundList.size());
// 再逐个比对关键属性
for (int i = 0; i < savedList.size(); i++) {
    Family saved = savedList.get(i);
    Family found = foundList.get(i);
    assertEquals(saved.getId(), found.getId());
    assertEquals(saved.getPersonId(), found.getPersonId());
    assertEquals(saved.getGroup(), found.getGroup());
    // 其他必要属性的断言
}

补充验证

  • 统一本地与Jenkins环境的H2版本,避免版本差异导致的行为不一致;
  • 启用SQL日志(spring.jpa.show-sql=true),更清晰观察ID生成与SQL执行过程。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 09:01:12