Spring data JPA: how to enable cascading deletion without reference to child in parent?

This may be a too simple question, but I am getting an exception when I try to delete a custom object.

Custom object:

@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 that references a user with a foreign key. I want that when the user is deleted, all objects PasswordResetToken

that refer to the user are also deleted. How can i do this?

@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`))

I would not like to add a link in PasswordResetToken

the parent, becaue User

should not know anything about PasswordResetToken

.

+3


source to share


3 answers


This is not possible at the JPA level without creating a bidirectional relationship. You need to specify the type of cascade in the class User

. User

must be the owner of the relationship and he must provide information on how to handle the related PasswordResetToken

.

But if you cannot have bi-directional communication, then I would recommend that you set the relationship directly in the schema generation of the SQL script.

If you create your schema through a SQL script and not through JPA autogeneration (I believe all serious projects should follow this pattern) you can add a constraint ON DELETE CASCADE

there.



It will look something 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 the DB migration tools with spring boot. And here is information on how to generate a script schema from hibernate (which will make it easier to write your own script).

+4


source


Parent object:

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

      

Child:



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

      

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

If you don't need the reference in the parent object (user), you need to override the default method Delete()

and write your own logic to find and remove the PasswordResetToken first and then the user.

+2


source


You can use Entity listener and callback method @PreRemove

to remove the associated "token" in front of 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 a very simple method of your "Token" repository:

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

      

Pay attention to the static declaration tokenRepository

- without this Spring cannot inject tokenRepository

, because as I understand it UserListener

is created by Hybernate (see more information here ).

Also, as we can read in the manual ,

the callback method must not call the EntityManager or Query methods!

But in my simple test everything works fine.

Work example and test .

+1


source







All Articles