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
Spring data JPA: how to enable cascading delete without a reference to the child in the parent?
提问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 PasswordResetToken
objects 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 PasswordResetToken
in the parent entity, becaue User
shouldn'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 User
class. User
should 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 CASCADE
constraint 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@PreRemove
to 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 deleteByPerson
is 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 TokenRepository
because, as I can understand, UserListener
is 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.
但在我的简单测试中,一切正常。