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

时间:2017-05-25 00:37:38

标签: java mysql jpa spring-boot spring-data-jpa

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

用户实体:

@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了解任何内容。

3 个答案:

答案 0 :(得分:5)

如果没有创建双向关系,则无法在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
);

以下是关于如何将数据库迁移工具与spring boot一起使用的the documentation。这里有the information关于如何从hibernate生成模式脚本(这将简化编写自己脚本的过程)。

答案 1 :(得分:3)

家长实体:

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

儿童实体:

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

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

忽略它

如果您不想在父实体(用户)中进行引用,则必须覆盖默认方法Delete()并编写逻辑以首先查找并删除 PasswordResetToken 然后是用户

答案 2 :(得分:2)

您可以使用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是'Token'存储库的非常简单的方法:

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

注意tokenRepository的静态声明 - 没有这个Spring无法注入TokenRepository,因为我可以理解,UserListener是由Hybernate实例化的(参见其他信息here })。

另外,我们可以阅读manual

  

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

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

工作exampletest