java Spring数据JPA:如何在不引用父级中的子级的情况下启用级联删除?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44170533/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 08:00:09  来源:igfitidea点击:

Spring data JPA: how to enable cascading delete without a reference to the child in the parent?

javamysqljpaspring-bootspring-data-jpa

提问by Bassinator

Maybe this is an overly simple question, but I am getting an exception when I try to delete a user entity.

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

The user entity:

用户实体:

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

And I have an entity class which references a user with a foreign key. What I want to happen is that when the user is deleted, any PasswordResetTokenobjects that reference the user are also deleted. How can I do this?

我有一个实体类,它使用外键引用用户。我想要发生的是,当用户被删除时,所有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;

The exception I am getting boils down to 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`))

我得到的例外归结为 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`))

I'd like to avoid adding a reference to PasswordResetTokenin the parent entity, becaue Usershouldn't need to know anything about PasswordResetToken.

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

回答by Sasha Shpota

It is not possible on JPA level without creating bidirectional relation. You need to specify cascade type in Userclass. Usershould be owner of the relation and it should provide the information on how to deal with related PasswordResetToken.

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

But if you can't have bidirectional relation I would recommend you to setup relation directly in schema generation SQL script.

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

If you create your schema via SQL script and not via JPA autogeneration (I believe all serious projects must follow this pattern) you can add ON DELETE CASCADEconstraint there.

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

It will look somehow like this:

它看起来像这样:

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

Here is the documentationon how to use DB migration tools with spring boot. And here is the informationon how to generate schema script from hibernate (that will simplify the process of writing your own script).

这是有关如何在 Spring Boot 中使用数据库迁移工具的文档。这是有关如何从休眠生成模式脚本的信息(这将简化编写自己的脚本的过程)。

回答by zeagord

Parent Entity:

父实体:

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

Child Entity:

子实体:

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

If you want the Password entity to be hidden from the client, you can write a custom responses and hide it. Or if you want to ignore it by using @JsonIgnore

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

If you don't want the reference in the Parent Entity (User), then you have to override the default method Delete()and write your logic to find and delete the PasswordResetTokenfirst and then the User.

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

回答by Cepr0

You can use Entity listener and Callback method@PreRemoveto delete an associated 'Token' before the 'User'.

您可以使用实体侦听器和回调方法@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);
    }
}

where deleteByPersonis very simple method of your 'Token' repository:

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

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

Pay attention on static declaration of tokenRepository- without this Spring could not inject TokenRepositorybecause, as I can understand, UserListeneris instantiated by Hybernate (see additional info here).

注意静态声明tokenRepository- 没有这个 Spring 无法注入,TokenRepository因为据我所知,它UserListener是由 Hybernate 实例化的(请参阅此处的其他信息)。

Also as we can read in the manual,

同样,正如我们在手册中所读到的,

a callback method must not invoke EntityManager or Query methods!

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

But in my simple test all works OK.

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

Working exampleand test.

工作示例测试