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

JPA无法删除Campaign实体问题排查及疑问

Spring Data JPA中自定义remove方法未执行删除的问题分析

问题场景

使用PostgreSQL数据库时,删除Campaign实体遇到异常:

  • 调用CampaignRepository的removeByIdAndUserId方法,实际执行的是SELECT语句而非DELETE;
  • 切换为deleteByIdAndUserId方法则无日志输出;
  • 仅添加@Modifying和@Query注解自定义删除语句后,才能正常执行删除。
  • 对比使用long类型主键的Note实体,其removeById方法可正常删除,曾怀疑问题与UUID主键有关。

相关代码

Campaign实体(UUID主键)

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Campaign {
    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    private UUID id;
    private Long userId;
    private String name;
    @Enumerated(EnumType.STRING)
    private CalendarSystem calendarSystem;
    private Instant campaignDateTimeStart;
    private Instant campaignDateTimeCurrent;
    private LocalDate realDateStart;
    private LocalDate realDateLastPlayed;
}

原CampaignRepository(未生效)

@Repository
public interface CampaignRepository extends JpaRepository<Campaign, UUID> {
    Long removeByIdAndUserId(UUID id, Long userId);
}

修改后的CampaignRepository(生效)

@Repository
public interface CampaignRepository extends JpaRepository<Campaign, UUID> {
    @Modifying
    @Query("delete from Campaign c where c.id = :id and c.userId = :userId")
    int removeByIdAndUserId(UUID id, Long userId);
}

可正常运行的Note示例(long主键)

// Note实体
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Note {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private UUID campaignId;
    private String title;
    private String body;
}

// NoteRepository
@Repository
public interface NoteRepository extends JpaRepository<Note, Long> {
    List<Note> findAllByCampaignId(UUID campaignId);
    Long removeById(long id);
}

原因分析

问题核心并非UUID主键类型,而是Spring Data JPA对衍生查询方法的默认行为规则:

  1. 默认主键删除方法(如removeById):
    对于框架提供的removeById/deleteById这类单一主键匹配的方法,无论主键是long、UUID还是其他类型,都会直接生成DELETE FROM table WHERE id = ?语句,无需额外注解。这也是Note实体的removeById能正常工作的原因。

  2. 自定义多条件remove/delete方法:
    自定义removeByIdAndUserId这类多条件衍生方法时,框架默认行为是先执行SELECT查询匹配实体,再通过EntityManager删除这些实体:

    • 你看到的SELECT语句就是查询阶段的日志;
    • 无DELETE日志可能是因为日志未开启DEBUG级别(Hibernate删除操作日志默认在DEBUG级),或查询结果为空无实体可删。
  3. @Modifying注解的作用:
    添加@Modifying后,框架会将方法识别为批量更新/删除操作,直接生成执行DELETE语句,跳过先查后删的步骤,这也是修改后方法生效的原因。

解决方案

方案1:使用@Modifying + @Query(推荐)

直接执行DELETE语句,避免额外SELECT查询,适合批量或多条件删除场景,即你已实现的方式。

方案2:保留衍生方法风格

若希望沿用removeByIdAndUserId命名风格,无需@Query,但需注意:

  • 方法返回类型改为void或List<Campaign>(返回被删除的实体列表),衍生方法的Long返回值实际是查询到的实体数量,而非删除数量;
  • 确保方法在@Transactional注解的方法中调用(你的服务层已满足);
  • 开启Hibernate的DEBUG日志,即可看到后续DELETE语句。

示例:

@Repository
public interface CampaignRepository extends JpaRepository<Campaign, UUID> {
    List<Campaign> removeByIdAndUserId(UUID id, Long userId);
}

验证结论

给Note实体添加自定义多条件删除方法做验证:

@Repository
public interface NoteRepository extends JpaRepository<Note, Long> {
    Long removeByIdAndCampaignId(long id, UUID campaignId);
}

调用该方法时同样会先执行SELECT语句,与Campaign的情况完全一致,证明问题与UUID主键无关,只是自定义多条件衍生方法的默认行为。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 12:23:20