Spring data JPA: how to enable cascading delete without a reference to the child in the parent?
It is not possible on JPA level without creating a 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
.
But if you cannot have a bidirectional relation I would recommend you to setup relation directly in schema generation SQL script.
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.
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 documentation on how to use DB migration tools with spring boot. And here is the information on how to generate schema script from hibernate (that will simplify the process of writing your own script).
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
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 PasswordResetToken first and then the User.
You can use Entity listener and Callback method @PreRemove
to delete an associated 'Token' before the 'User'.
@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:
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).
Also as we can read in the manual,
a callback method must not invoke EntityManager or Query methods!
But in my simple test all works OK.
Working example and test.