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
相关产品推荐
相关产品推荐

