2017-05-25 66 views
3

也许这是一个过于简单的问题,但我在尝试删除用户实体时遇到了异常。Spring数据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; 

而且我有它引用与外键的用户的实体类。我想要发生的是当用户被删除时,任何引用用户的对象也被删除。我怎样才能做到这一点?

@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,becaue User应该不需要了解任何PasswordResetToken

+1

假设你看看这个[post](https://vladmihalcea.com/2015/03/05/a-beginners-guide-to-jpa-and-hibernate-cascade-types/),它解释更多关于你的问题和解决方案。 –

+0

假设你必须添加'@OneToOne(mappedBy =“user”,cascade = CascadeType.ALL,orphanRemoval = true)' –

+0

给用户实体?或者PasswordResetToken实体?我并不是真的想在User实体中添加任何引用,因为用户实体不需要知道重置令牌的存在。 – Airhead

回答

4

无法在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关于如何使用数据库迁移工具与春天开机。这里是the information关于如何从休眠生成模式脚本(这将简化编写自己的脚本的过程)。

+1

我同意你在DDL中需要它 - 然后未来的开发人员试图破译子记录在检查DDL之前是如何消失的 – farrellmr

2

父实体:

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

子实体:

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

如果你想密码实体从客户端被隐藏,你可以写一个自定义的响应和隐藏。或者,如果你想使用@JsonIgnore

忽略它,如果你不想在父实体(用户)的参考,那么你必须覆盖默认的方法Delete(),写你的逻辑来查找并删除PasswordResetToken首先是,然后是用户

1

您可以使用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是你的“令牌”仓库的非常简单的方法:对tokenRepository静态声明

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

,请注意 - 如果没有这个春天不能注入TokenRepository,因为我能理解,UserListener由Hybernate实例化(参见附加信息here)。

而且我们可以在manual阅读,

回调方法不能调用EntityManager的或查询方法!

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

工作exampletest