0

I am preparing simple Spring app. I have 2 entities : Book.class (parent) and Author.class (child): with @OneToMany from Author view and @ManyToOne(cascade=CascadeType.PERSIST) from Book view relations. While saving new Book - also Author is being saved and added to DB( mySql)- which is what I want. But I cannot understand why Spring adds Author - if such item already exists. How to change the code to make sure that only unique Authors will be added to DB and there will be no duplicates in author table in DB?

I've added hashCode and equals methods to Author class but it did not help. I've tried to change also Cascade.Type but also did not help.

The Author.class(part of code):

@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author")
@JsonManagedReference(value = "book-author")
private Set<Book> books = new HashSet<Book>();
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Author author = (Author) o;
    return Objects.equals(getFirstName(), author.getFirstName()) &&
            Objects.equals(getLastName(), author.getLastName());
    }

    @Override
    public int hashCode() {
    return Objects.hash(getFirstName(), getLastName());
    }

And the Book.class(part of code):

@Entity
public class Book {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String title;

@ManyToOne( cascade=CascadeType.PERSIST)
@JoinColumn(name = "author_id", unique = true)
@JsonBackReference(value="book-author")
private Author author;

Edit 1 BookServiceImpl.class

@Override
 public BookDto addBookDto(BookDto bookDto) {
    Book book = bookConverter.apply(bookDto);
    bookRepository.save(book);
    return bookDtoConverter.apply(book);
}

AuthorServiceImpl.class

@Override
 public Author findAuthor(String firstName, String lastName) {
    Optional<Author> authorByNameOptional =      authorRepository.findByFirstNameAndLastName(firstName, lastName);
    if (authorByNameOptional.isPresent()) {

        return authorByNameOptional.get();
    } else {
        Author newAuthor = new Author();
        newAuthor.setFirstName(firstName);
        newAuthor.setLastName(lastName);
        return newAuthor;
    }

And BookWebController.class

@PostMapping("/addWebBook")
    public String addBook(@ModelAttribute(name = "addedBook") BookDto addedBook, Model model) {
        Author author1 = addedBook.getAuthor();
        Author author = authorService.findAuthor(author1.getFirstName(), author1.getLastName());
        addedBook.setAuthor(author);
        bookService.addBookDto(addedBook);
        return "redirect:/message?msg";
    }

Would be greatful for any hint as I am quite new to this area :-)

pastapola
  • 1
  • 1
  • 3

1 Answers1

3
  1. Let me suggest that your Author object in Book has empty primary key field? Hibernate's logic in this case: empty id means new row to insert. It may work correctly if you set a primary key to author. For example, user can find author (with it's PK) or add new (without PK) and call save(book) method which will cascadely persists only new author. In most cases that usually works like that.
  2. Another moment to pay attention, that if you wanna keep author's uniqueness in database, than you must to put constraint on the author's entity. For example if each author must have unique first name and last name it may look something like this:

    @Entity
    @Table(uniqueConstraints= @UniqueConstraint(columnNames={"first_name", "last_name"}))
    public class Author {
    ...
    

    After that, DataIntegrityViolationException will be thrown on duplicating value insertion and your database will stay clean of duplicates.

Artur Vakhrameev
  • 794
  • 7
  • 11
  • Thank you. I've tried as you suggested to look for Id of existing Author (basing on given last name and first name), which then I've used in save method from CRUD respository. But what surprised me - when I've tried to save Book with this existing Author - the method save did not work as I supposed it should be. It did not update record of Author but throw ConstraintViolationException with info there's a duplicate entry for this key... It gives me this error no matter if I put these unique constrains you suggested or not. Due to problem with child entity I cannot save parent entity (Book)... – pastapola Jan 21 '19 at 19:06
  • Yes, sure- already added in Edit 1.I am wondering - maybe it is a problem with cascade? – pastapola Jan 22 '19 at 08:19
  • Update on my side :-) I have changed Cascade on Author field to ( cascade={CascadeType.MERGE,CascadeType.PERSIST},fetch=FetchType.EAGER) and removed unique = true. It helped :-) Now it works. Thanks man for your help:-) – pastapola Jan 22 '19 at 09:31
  • PERSIST won't work if your Author Object was detached, and MERGE will help you in that case, because it returns object back to managed state, but it also can update your existing value by passed id if you chaged some other field. – Artur Vakhrameev Jan 22 '19 at 09:47