Spring data JPA:如何启用级联删除而不引用父级中的子级?

Posted

技术标签:

【中文标题】Spring data JPA:如何启用级联删除而不引用父级中的子级?【英文标题】:Spring data JPA: how to enable cascading delete without a reference to the child in the parent? 【发布时间】:2017-10-25 12:22:06 【问题描述】:

也许这是一个过于简单的问题,但是当我尝试删除用户实体时遇到异常。

用户实体:

@Entity
@Table(name = "users")
public class User 

    @Transient
    private static final int SALT_LENGTH = 32;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @NotNull
    private String firstName;

    @NotNull
    private String lastName;

    @Column(unique = true, length = 254)
    @NotNull
    private String email;

    // BCrypt outputs 60 character results.
    @Column(length = 60)
    private String hashedPassword;

    @NotNull
    private String salt;

    private boolean enabled;

    @CreationTimestamp
    @Temporal(TemporalType.TIMESTAMP)
    @Column(updatable = false)
    private Date createdDate;

我有一个实体类,它使用外键引用用户。我想要发生的是,当用户被删除时,任何引用用户的 PasswordResetToken 对象也会被删除。我该怎么做?

@Entity
@Table(name = "password_reset_tokens")
public class PasswordResetToken 

    private static final int EXPIRATION_TIME = 1; // In minutes

    private static final int RESET_CODE_LENGTH = 10;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String token;

    @OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
    @JoinColumn(nullable = false, name = "userId")
    private User user;

    private Date expirationDate;

我得到的异常归结为Cannot delete or update a parent row: a foreign key constraint fails (`heroku_bc5bfe73a752182`.`password_reset_tokens`, CONSTRAINT `FKk3ndxg5xp6v7wd4gjyusp15gq` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))

我想避免在父实体中添加对PasswordResetToken 的引用,因为User 不需要知道关于PasswordResetToken 的任何信息。

【问题讨论】:

假设您看一下这个post,它解释了有关您的问题和解决方案的更多信息。 假设你必须添加@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) 到用户实体?还是 PasswordResetToken 实体?我真的不想在 User 实体中添加对子项的任何引用,因为用户实体不需要知道重置令牌的存在。 PasswordResetToken 我收到Unknown MappedBy 错误。为什么? 【参考方案1】:

如果不创建双向关系,在 JPA 级别上是不可能的。您需要在User 类中指定级联类型。 User 应该是关系的所有者,它应该提供有关如何处理相关PasswordResetToken 的信息。

但如果你不能建立双向关系,我建议你直接在模式生成 SQL 脚本中设置关系。

如果您通过 SQL 脚本而不是通过 JPA 自动生成来创建架构(我相信所有严肃的项目都必须遵循这种模式),您可以在此处添加 ON DELETE CASCADE 约束。

它看起来像这样:

CREATE TABLE password_reset_tokens (
  -- columns declaration here
  user_id INT(11) NOT NULL,
  CONSTRAINT FK_PASSWORD_RESET_TOKEN_USER_ID
  FOREIGN KEY (user_id) REFERENCES users (id)
    ON DELETE CASCADE
);

这里是the documentation,介绍如何在 Spring Boot 中使用 DB 迁移工具。这里是 the information 关于如何从 hibernate 生成模式脚本(这将简化编写自己的脚本的过程)。

【讨论】:

我同意你在 DDL 中需要它——那么未来的开发人员会在检查 DDL 之前尝试破译子记录是如何消失的【参考方案2】:

父实体:

@OneToOne
@JoinColumn(name = "id")
private PasswordResetToken passwordResetToken;

子实体:

@OneToOne(mappedBy = "PasswordResetToken", cascade = CascadeType.ALL, orphanRemoval = true)
private User user;

如果您希望对客户端隐藏密码实体,您可以编写自定义响应并将其隐藏。或者,如果您想使用 @JsonIgnore 忽略它

如果您不希望父实体(用户)中的引用,那么您必须覆盖默认方法 Delete() 并编写您的逻辑以先查找和删除 PasswordResetToken 然后用户

【讨论】:

【参考方案3】:

您可以使用Entity listener and Callback method @PreRemove 在“用户”之前删除关联的“令牌”。

@EntityListeners(UserListener.class)
@Entity
public class User 

    private String name;


@Component
public class UserListener 

    private static TokenRepository tokenRepository;

    @Autowired
    public void setTokenRepository(TokenRepository tokenRepository) 
        PersonListener.tokenRepository = tokenRepository;
    

    @PreRemove
    void preRemove(User user) 
        tokenRepository.deleteByUser(user);
    

deleteByPerson 是您的“令牌”存储库的非常简单的方法:

public interface TokenRepository extends JpaRepository<Token, Long> 
    void deleteByUser(User user);
 

注意tokenRepository 的静态声明——没有这个 Spring 就无法注入 TokenRepository,因为据我所知,UserListener 是由 Hybernate 实例化的(请参阅附加信息 here)。

我们也可以在manual 中看到,

回调方法不得调用 EntityManager 或 Query 方法!

但在我的简单测试中,一切正常。

工作example 和test。

【讨论】:

以上是关于Spring data JPA:如何启用级联删除而不引用父级中的子级?的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring JPA 中删除实体

如何使用 Spring Data Jpa 启用多租户

Spring Data JPA:保存嵌套的 OneToMany 子级(具有级联)返回 NULL 父级外部对象

spring data jpa:插入而不是删除

你不会还搞不清楚Spring Data JPA的关联关系注解如何使用吧?

你不会还搞不清楚Spring Data JPA的关联关系注解如何使用吧?