Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ConcurrentModificationException when deleting a @ManyToMany associations Spring Data Jpa

i'm getting crazy with this... I followed all articles from Thoughts on Java and Vlad mihalcea about mapping associations in hibernate, but i don't know why deleting in @ManyToMany, is somehow always messed up in my project about Rest services with Spring Data Jpa.

I have a basic book -> authors manytomany relationhip, that i think i mapped correctly (at least according to how they recommand in the two references quoted higher).

I override equals, and hashcode, i wrote and use the methods to manage the associations between entities, but when i try to remove books from authors (that i want to delete), i got a ConcurrentModificationException: null, even if i use an iterator.

My entities :

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotNull
    @Size( min = 2, max = 30)
    @Column (length = 30)
    private String firstName;

    @Size( min = 2, max = 30)
    @Column (length = 30)
    private String lastName;

    @ManyToMany(mappedBy = "authors")
    private Set<Book> books = new HashSet<>();

    public Author(@NotNull @Size(min = 2, max = 30) String firstName, @NotNull @Size(min = 2, max = 30) String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public void addBook(Book book){
        this.books.add(book);
        book.getAuthors().add(this);
    }

    public void removeBook(Book book){
        this.books.remove(book);
        book.getAuthors().remove(this);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null) return false;
        if (this.getClass() != o.getClass()) return false;
        return id != null && id.equals(((Author)o).getId());
    }
    @Override
    public int hashCode() {
        return 17;
    }
}

@Entity
@Getter
@Setter
@NoArgsConstructor
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String isbn;
    @NotNull
    private String title;

    //Owning side
    @NotNull
    @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
    @JoinTable(name = "book_author",
               joinColumns = @JoinColumn(name = "book_id"),
               inverseJoinColumns = @JoinColumn(name = "author_id"))
    private Set<Author> authors = new HashSet<>();

    public Book(String title) {
        this.title = title;
        this.isbn = RandomStringUtils.randomAlphanumeric(10);
    }

    public void addAuthor(Author author){
        this.authors.add(author);
        author.getBooks().add(this);
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null) return false;
        if (this.getClass() != o.getClass()) return false;
        return id != null && id.equals(((Book)o).getId());
    }
    @Override
    public int hashCode() {
        return 13;
    }
}

My deleting method which is in my business layer :


    public void deleteAuthor(Long id) {
        Optional<Author> author = authorRepository.findById(id);
        if (author.isPresent()){
            Author au = author.get();
            for (Book book : au.getBooks()) {
                au.removeBook(book);
            }
            authorRepository.delete(au);
        }
    }

I have the exception after the first loop in the for each. It's written exactly as in the examples, so i have absolutely no idea. I wonder if it's the Spring Data Layer that makes it more complicated ? Thanks for your help !

EDIT: The exception appears just if my author own more than one book

like image 227
Alain Duguine Avatar asked Sep 08 '25 10:09

Alain Duguine


1 Answers

Apparently i managed to solve the problem by adding a new hash set while iterating.

    public void deleteAuthor(Long id) {
        Optional<Author> author = authorRepository.findById(id);
        if (author.isPresent()) {
            for (Book book : new HashSet<Book>(author.get().getBooks())) {
                author.get().removeBook(book);
            }
            authorRepository.delete(author.get());
        }
    }

but it's strange that i have to do that, in all examples i saw, there is nothing like that.

like image 59
Alain Duguine Avatar answered Sep 10 '25 01:09

Alain Duguine