JPA: удаление с помощью внешних ключей в отношениях ManyToMany и ManyToOne? - PullRequest
1 голос
/ 08 мая 2020

Я использую пружинную загрузку для моделирования своего бэкэнда, и у меня есть несколько внешних ключей в структуре моей модели.

Я не могу выполнять операции удаления в целом и операции обновления, когда затрагиваются внешние ключи. Я пробовал несколько методов, найденных на inte rnet, но, похоже, ни один из них не работает.

enter image description here

Через SQL (SQL команды) могут удалить объекты из book_queue_entry, copy и author_write, но не пользователя author, book и book_store из-за ограничений внешнего ключа. Поэтому я хотел бы знать, что я делаю неправильно в каждой из моих моделей / отношений, которые не работают на уровне SQL. Поскольку это не работает на уровне SQL, что-то с моим моделированием, по крайней мере, что касается настройки операций удаления в корне неверно. Я не могу указать на что.

Проблема 1: Невозможно удалить книгу: при удалении книги ее следует удалить из списка произведений автора - другими словами, удалить ее из списка авторов_write:

Автор. java:

@Entity
@Setter
@Getter
@NoArgsConstructor
public class Author extends BaseEntity implements Serializable {

    //stuff

    @ManyToMany(cascade = CascadeType.ALL)
    @JsonIgnore
    @JoinTable(
            name = "authors_write",
            joinColumns = @JoinColumn(name = "author_id"),
            inverseJoinColumns = @JoinColumn(name = "book_id"))
    Set<Book> works;
}

Книга. java

@Entity
@Setter
@Getter
@NoArgsConstructor
public class Book extends BaseEntity implements Serializable {

    //stuff

    @ManyToMany(mappedBy = "works", cascade = CascadeType.ALL)
    @EqualsAndHashCode.Exclude
    Set<Author> authors;

    @JsonIgnore
    @OneToMany(mappedBy = "book")
    private List<BookQueueEntry> bookQueue;
}

Проблема 2: Невозможно удалить авторов. Когда автор удаляется, все записи в авторах_write с соответствующим author_id должны быть удалены. Классы такие же, как указано выше. В этом случае каскадирование также не работает.

Проблема 3: Невозможно удалить пользователей. При удалении пользователей заемщик_id в копии должен быть обнулен (я читал, что это не работает в JPA вообще), а book_queue_entries с соответствующим user_id должны быть удалены

Копия. java

@Entity
@Setter
@Getter
@NoArgsConstructor
public class Copy extends BaseEntity implements Serializable {

    //stuff

    @ManyToOne
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn
    @EqualsAndHashCode.Exclude
    private Book reference;

    @ManyToOne
    @JoinColumn
    @EqualsAndHashCode.Exclude
    private BookStoreUser borrower;
}

User. java:

public class BookStoreUser extends BaseEntity implements Serializable {

    //more stuff here 

    @JsonIgnore
    @OneToMany(mappedBy = "borrower")
    private Set<Copy> booksBorrowed;

}

BookQueueEntry. java

@Entity
@Getter
@Setter
@NoArgsConstructor
@EqualsAndHashCode
public class BookQueueEntry extends BaseEntity implements Serializable {

    //more stuff here

    @ManyToOne(cascade = CascadeType.ALL)
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn(name = "user_id", nullable = false)
    @EqualsAndHashCode.Exclude
    private BookStoreUser user;

    @ManyToOne
    @OnDelete(action = OnDeleteAction.CASCADE)
    @JoinColumn(name = "book_id", nullable = false)
    @EqualsAndHashCode.Exclude
    private Book book;


}

Еще раз, я считаю, что что-то не так с тем, как я сопоставил каскадные операции. Что-то не так в более общем смысле, и я не могу понять, что именно.

РЕДАКТИРОВАТЬ: Я должен отметить, что @OnDelete(action = OnDeleteAction.CASCADE) у меня тоже не работал. РЕДАКТИРОВАТЬ 2: С ясной головой я смог исправить проблемы 1 и 2, используя это: https://www.youtube.com/watch?v=vYNdjtf7iAQ

1 Ответ

0 голосов
/ 08 мая 2020

Как указано в комментариях, следите за этим видео: https://www.youtube.com/watch?v=vYNdjtf7iAQ

Мои проблемы решены, но при удалении пользователей возникает исключение ConcurrentModificationException, которое на самом деле не связано с этой проблемой.

Мои классы теперь выглядят так:

Книга. java

public class Book extends BaseEntity implements Serializable {

    @Column(unique = true)
    private String isbn;

    @NotBlank
    private String title;

    private Integer year;

    private String imageUrl;

    @ManyToMany(mappedBy = "works")
    @EqualsAndHashCode.Exclude
    Set<Author> authors;

    private Boolean isAvailable;

    private Integer version;

    @Enumerated(EnumType.STRING)
    private Language language;

    @JsonIgnore
    @OneToMany(mappedBy = "reference", cascade = CascadeType.REMOVE, orphanRemoval = true)
    private Set<Copy> copies;

    @JsonIgnore
    @OneToMany(mappedBy = "book", cascade = CascadeType.REMOVE, orphanRemoval = true)
    private List<BookQueueEntry> bookQueue;

    // see here why: https://www.youtube.com/watch?v=vYNdjtf7iAQ
    public void addAuthor(Author author) {
        this.authors.add(author);
        author.getWorks().add(this);
    }

    public void removeAuthor(Author author) {
        this.authors.remove(author);
        author.getWorks().remove(this);
    }
}

Автор. java:

public class Author extends BaseEntity implements Serializable {

    private String name;

    @ManyToMany
    @JsonIgnore
    @JoinTable(
            name = "authors_write",
            joinColumns = @JoinColumn(name = "author_id"),
            inverseJoinColumns = @JoinColumn(name = "book_id"))
    Set<Book> works;

    public void addBook(Book book) {
        this.works.add(book);
        book.addAuthor(this);
    }

    public void removeBook(Book book) {
        this.works.remove(book);
        book.removeAuthor(this);
    }
}

Копия. java:

public class Copy extends BaseEntity implements Serializable {

    @ManyToOne
    @JoinColumn
    @EqualsAndHashCode.Exclude
    private Book reference;

    private LocalDate borrowedAt;

    private LocalDate dueDate;

    @ManyToOne
    @JoinColumn
    @EqualsAndHashCode.Exclude
    private BookStoreUser borrower;

    @NotNull
    @Enumerated(EnumType.STRING)
    private Location location;

    public void addBorrower(BookStoreUser user) {
        user.addBorrowedCopy(this);
    }

    public void removeBorrower(BookStoreUser user) {
        user.removeBorrowedCopy(this);
    }
}

BookStoreUser. java

public class BookStoreUser extends BaseEntity implements Serializable {

    @NotNull
    @Column(unique = true)
    private String email;

    private String firstName;

    @NotNull
    private String lastName;

    private boolean isAdmin;

    @JsonIgnore
    @OneToMany(mappedBy = "borrower", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Copy> booksBorrowed;

    @NotNull
    private String password;

    public void addBorrowedCopy(Copy copy) {
        this.booksBorrowed.add(copy);
        copy.setBorrower(this);
        copy.setBorrowedAt(LocalDate.now());
        copy.setDueDate(LocalDate.now().plusMonths(1));
    }

    public void removeBorrowedCopy(Copy copy) {
        this.booksBorrowed.remove(copy);
        copy.setBorrower(null);
        copy.setBorrowedAt(null);
        copy.setDueDate(null);
    }
}

А также обязательно используйте служебные методы на уровне обслуживания, например:

public Long deleteById(Long id) {
    this.validator.checkIDNotNull(id);
    Book book = em.find(Book.class, id);
    for (Author author : book.getAuthors()) {
        book.removeAuthor(author);
    }
    bookRepository.deleteById(id);
    validator.checkEntityNotExists(id);
    return id;
}

public Book create(Book book) {
    this.checkISBNIsValid(book.getIsbn());
    this.checkISBNExists(book.getIsbn());
    this.checkEntityHasValues(book);
    for (Author author : book.getAuthors()) {
        author.addBook(book);
    }
    log.info("Book with ISBN {} created successfully", book.getId());
    return bookRepository.save(book);
}

EDIT: ConcurrentModificationException: удаление из списка / Set вызовет эту проблему. Это можно найти на SO - чтобы исправить исключения ConcurrentModificationExceptions, вам нужно создать новый HashSet с тем, что вызывает эту проблему, и повторить это:

new HashSet<Author>(book.getAuthors())
        .forEach(author -> book.removeAuthor(author));
...