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

测试CRUD Repository的save()方法时出现ObjectOptimisticLockingFailureException错误

问题描述

执行自定义Repository的save()方法测试时抛出乐观锁异常:

org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [ru.clevertec.newsonline.entity.News#7289c37a-8bfa-4092-872b-f1e4a8c319c6]

其他方法测试正常,已尝试添加@Commit注解但无效。

相关代码

NewsRepository

@Repository
public interface NewsRepository extends JpaRepository<News, UUID>, IRepository<News>, IFilterEntityRepository<News, NewsFilter> {

}

IRepository接口

public interface IRepository<E> {

    List<E> findAll();

    Page<E> findAll(Pageable pageable);

    Optional<E> findById(UUID id);

    E save(E e);

    void delete(E e);

}

测试类

@DataJpaTest
@ActiveProfiles("test")
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
class NewsRepositoryIT {
@Test
    public void shouldSaveNews() {
        //given
        News newNews = NewsTestBuilder.builder().build().buildNewsForSave();
        //when
        newsRepository.save(newNews);
        //then
        assertEquals(21, newsRepository.findAll().size());
        assertTrue(((CrudRepository<News, UUID>) newsRepository).findById(newNews.getNewsId()).isPresent());
    }
}

News实体类

@ToString(exclude = {"pictures", "comments"})
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
@Table(name = "news", schema = "news_online")
public class News {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "news_id")
    private UUID newsId;
    @Column(name = "header_news")
    private String headerNews;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "author_id")
    private Author author;
    @Column(name = "date_of_news", updatable = false)
    @ColumnDefault(value = "CURRENT_TIMESTAMP")
    private LocalDateTime dateOfNews;
    @Column(name = "is_published")
    private boolean isPublished;
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_id")
    private Category category;
    @Column(name = "short_description", length = 500)
    private String shortDescription;
    @Column(name = "content_link")
    private String contentLink;
    @ManyToMany(mappedBy = "news", cascade = CascadeType.ALL)
    @Fetch(value = FetchMode.SUBSELECT)
    private List<Picture> pictures;
    @OneToMany(mappedBy = "news", cascade = CascadeType.ALL, orphanRemoval = true)
    @Fetch(value = FetchMode.SUBSELECT)
    private List<Comment> comments;

    public void addPicture(Picture picture) {
        List<News> news = picture.getNews();
        news.add(this);
        picture.setNews(news);
        pictures.add(picture);
    }

    public void addComment(Comment comment) {
        comment.setNews(this);
        comments.add(comment);
    }

}

NewsTestBuilder工具类

public News buildNewsForSave() {
    return News.builder().newsId(UUID.fromString("7289c37a-8bfa-4092-872b-f1e4a8c319c6")).headerNews("Breaking news")
            .isPublished(true).category(Category.builder().categoryId(UUID.fromString("3de78717-14f5-491e-a375-e122ec4dcbe2")).section(Section.CAR).build())
            .shortDescription("Text").contentLink("static/text/news100.txt").build();
}
原因分析
  1. 手动指定ID触发更新操作:News实体的ID配置了@GeneratedValue(strategy = GenerationType.AUTO),本应由JPA自动生成ID,但测试中通过构建器手动设置了固定ID。JPA会将带有已存在ID的实体判定为已持久化对象,执行update而非insert。如果测试数据库中该ID的记录已存在,或测试过程中该记录被修改/删除,就会触发乐观锁异常。

  2. 关联实体状态异常:测试中构建的Category仅设置了ID,未被持久化上下文管理。执行save时JPA会处理关联的Category,若该Category不存在于数据库,会引发级联操作异常;若存在,也可能因关联实体状态不一致间接导致当前News的更新操作失败。

  3. 测试数据残留:即使添加了@Commit,若之前的测试已将该ID的记录提交到数据库,本次测试再次尝试更新时,数据库中记录的状态(如版本号)与实体状态不一致,也会抛出异常。

解决方案
  1. 移除手动设置的ID:修改构建方法,让JPA自动生成ID:
public News buildNewsForSave() {
    return News.builder()
            .headerNews("Breaking news")
            .isPublished(true)
            .category(Category.builder().categoryId(UUID.fromString("3de78717-14f5-491e-a375-e122ec4dcbe2")).section(Section.CAR).build())
            .shortDescription("Text")
            .contentLink("static/text/news100.txt")
            .build();
}
  1. 确保关联实体存在:测试前先通过categoryRepository.save()将构建的Category存入数据库,或使用@Sql注解提前插入测试数据,避免关联实体不存在引发的异常。

  2. 清理测试数据:利用@DataJpaTest默认的@Rollback特性,确保每次测试后数据回滚;或在@BeforeEach方法中清理News表中对应ID的记录,避免残留数据干扰。

  3. 检查乐观锁配置:若实体使用了@Version字段,确保测试时不手动指定版本号,让JPA自动维护版本信息。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 03:27:07