2017-05-25 59 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