Spring JPA无法删除Review实体问题求助
Review实体删除无效果且无异常的排查与解决
问题描述
尝试删除Review实体时,既未删除对应数据,也未抛出任何异常。调整过部分级联类型后仍无法定位问题,使用CrudRepository自带的需传入对象引用的delete方法同样无效。
相关代码
实体类
Product类
@Entity(name = "product_postgresql") @Getter public class Product { @Id @Column(nullable = false, unique = true) private String sku; @Column(nullable = false) private String designation; @Column(nullable = false) private String description; @Column(nullable = false) private double aggregatedRating; @OneToMany(cascade = CascadeType.ALL, mappedBy = "product", fetch = FetchType.LAZY) private List<Review> reviews; protected Product() { } public Product(final String sku, final String designation, final String description, final double aggregatedRating) { this.sku = sku; this.designation = designation; this.description = description; this.aggregatedRating = aggregatedRating; } }
Review类
@Getter @Entity(name = "review_postgresql") public class Review { @Id private String id; @Version private long version; @Column(nullable = false) private String approvalStatus; @Column(nullable = false) private String text; @OneToMany(cascade = CascadeType.ALL, mappedBy = "review", fetch = FetchType.EAGER) @Setter private List<Vote> votes; private String report; @Column(nullable = false) private LocalDate publishingDate; @Column(nullable = false) private String funFact; @Column(nullable = false) private double rating; @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REFRESH) @JoinColumn(name = "product_sku", nullable = false) private Product product; @ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.REFRESH) @JoinColumn(name = "user_id", nullable = false) private User user; protected Review() { } public Review(final String id, final long version, final String approvalStatus, final String text, final String report, final LocalDate publishingDate, final String funFact, final Double rating, final Product product, final User user) { this.id = id; this.version = version; this.approvalStatus = approvalStatus; this.text = text; this.report = report; this.publishingDate = publishingDate; this.funFact = funFact; this.rating = rating; this.product = product; this.user = user; } }
User类
@Entity(name = "user_postgresql") @Getter public class User { @Id private String id; @Email @Column(nullable = false, unique = true) private String email; @Column(nullable = false) private String password; @Column(nullable = false) private String fullName; @Column(nullable = false) private String role; //why multiple roles? @Column(nullable = false, unique = true) private String nif; @Column(nullable = false) private String address; @OneToMany(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL) private List<Review> reviews; @OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private List<Vote> votes; protected User() { } public User(final String id, final String email, final String password, final String fullName, final String role, final String nif, final String address) { this.id = id; this.email = email; this.password = password; this.fullName = fullName; this.role = role; this.nif = nif; this.address = address; } }
Vote类
@Entity(name = "vote_postgresql") @Getter public class Vote implements Serializable { @EmbeddedId private VoteKey id; @Column(nullable = false) private String vote; @ManyToOne(fetch = FetchType.LAZY) @MapsId("user_id") @JoinColumn(name = "user_id", nullable = false) private User user; @ManyToOne(fetch = FetchType.LAZY) @MapsId("review_id") @JoinColumn(name = "review_id", nullable = false) private Review review; protected Vote() { } public Vote(User user, Review review, String vote) { this.id = new VoteKey(user.getId(), review.getId()); this.user = user; this.review = review; this.vote = vote; } }
JPA仓库
@ConditionalOnProperty(value="spring.repositories.targetPackage", havingValue = "postgresql") public interface ReviewRepositoryPostgresql extends CrudRepository<Review, String> { @Modifying @Query("DELETE FROM review_postgresql r WHERE r.id = :id") void deleteById(String id); }
排查与解决要点
- 事务必须覆盖删除操作:自定义的
@Modifying删除方法必须在事务中执行,要么在Repository方法上添加@Transactional注解,要么在调用该方法的Service层方法上添加@Transactional。没有事务的话,JPA不会把删除操作提交到数据库。 - 处理Vote关联数据的级联删除:Review与Vote是一对多关联,且Vote的主键包含review_id,直接删除Review会触发数据库外键约束。可以在Review的
@OneToMany注解中添加orphanRemoval = true,确保删除Review时自动删除关联的Vote;也可以在数据库的外键约束上设置ON DELETE CASCADE。 - 修正JPQL语句规范:虽然当前JPQL用表别名
review_postgresql是可行的,但更规范的写法是使用实体类名(Review),修改为DELETE FROM Review r WHERE r.id = :id,避免混淆。 - 注意CrudRepository默认方法的使用条件:使用
delete(Review entity)时,必须传入从数据库查询出来的持久化状态对象,不能是自行new的对象;同时,因为User与Review是双向关联,删除Review前要先从User的reviews列表中移除该Review,否则User的持久化上下文会保留引用,导致删除操作失效。 - 开启SQL日志排查执行情况:在配置文件中开启JPA的SQL日志,查看是否实际执行了DELETE语句,以及是否有隐藏的约束错误:
spring: jpa: show-sql: true properties: hibernate: format_sql: true
- 检查乐观锁版本号:Review实体带有
@Version乐观锁注解,如果删除时传入的实体版本号与数据库中不一致,会触发OptimisticLockingFailureException,但如果事务未提交,异常可能被吞掉,需确认版本号是否正确。
内容的提问来源于stack exchange,提问作者Simão Santos
相关产品推荐
相关产品推荐

