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

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

用户实体:

@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; 

我得到的exception归结为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任何信息。

在没有创建双向关系的情况下,在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一起使用的文档 。 以下是有关如何从hibernate生成模式脚本的信息 (这将简化编写自己脚本的过程)。

父实体:

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

儿童实体:

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

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

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

您可以使用Entity侦听器和Callback方法 @PreRemove删除“User”之前的关联“Token”。

 @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 { void deleteByUser(User user); } 

注意tokenRepository静态声明 – 没有这个Spring不能注入TokenRepository ,因为我可以理解, UserListener是由Hybernate实例化的(参见这里的附加信息)。

另外我们可以在手册中阅读,

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

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

工作实例和测试 。