Okay Atalay
Okay Atalay

Reputation: 111

Hibernate ManyToMany works like OneToMany

I' m tring to make an example to see table output of ManyToMany relationship managed by Hibernate. Everthing seems well. However, some rows are duplicated. I do not know why. My Scenario is that I have 2 simple Classes Author & Book. Book can have more than one author and Author can have more than one book.

Let' s see classes and mysql table output.

Book.java

@Entity
@Table(name = "BOOK")
public class Book {

@SequenceGenerator(name = "SEQ_GEN_BOOK", allocationSize = 1, sequenceName = "TABLE_SEQ_GEN_BOOK", initialValue = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_GEN_BOOK")
@Id
@Column(name = "ID")
private long id;

@Column(name = "NAME", nullable = false, length = 100)
private String name;

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, targetEntity = Author.class)
private Set authors = new HashSet();

//setter/getters

Author.java

@Entity
@Table(name = "Author")
public class Author {

@SequenceGenerator(name = "SEQ_GEN_AUTHOR", initialValue = 1, sequenceName = "TABLE_SEQ_GEN_AUTHOR", allocationSize = 1)
@GeneratedValue(generator = "SEQ_GEN_AUTHOR", strategy = GenerationType.SEQUENCE)
@Id
@Column(name = "ID")
private long id;

@Column(name = "NAME", nullable = false, length = 100)
private String name;

@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "authors")
private Set<Book> books = new HashSet<Book>();

//setter/getters

main method is

 public static void main(String[] args) throws InterruptedException {
    Author author1 = new Author();
    author1.setName("author1");
    Author author2 = new Author();
    author2.setName("author2");
    Author author3 = new Author();
    author3.setName("author3");

    Book book1 = new Book();
    book1.setName("book1");
    Book book2 = new Book();
    book2.setName("book2");
    Book book3 = new Book();
    book3.setName("book3");
    Book book4 = new Book();
    book4.setName("book4");

    book1.getAuthors().add(author1);
    book1.getAuthors().add(author2);

    book2.getAuthors().add(author1);
    book2.getAuthors().add(author2);
    book2.getAuthors().add(author3);

    book3.getAuthors().add(author2);
    book3.getAuthors().add(author3);

    book4.getAuthors().add(author3);
    DbOperations.getInstance().save(book1);
    DbOperations.getInstance().save(book2);
    DbOperations.getInstance().save(book3);
    DbOperations.getInstance().save(book4);

 }

Mysql Table output is below: (i can create the same overview without having transition table)

mysql> show tables;
+------------------------+
| Tables_in_hibernatedb1 |
+------------------------+
| author                 |
| book                   |
| book_author            |
| table_seq_gen_author   |
| table_seq_gen_book     |
+------------------------+
5 rows in set (0.00 sec)

mysql> select * from book_author;
+----------+------------+
| books_ID | authors_ID |
+----------+------------+
|        1 |          1 |
|        1 |          2 |
|        2 |          3 |
|        2 |          4 |
|        2 |          5 |
|        3 |          6 |
|        3 |          7 |
|        4 |          8 |
+----------+------------+
8 rows in set (0.00 sec)

mysql> select * from book;
+----+-------+
| ID | NAME  |
+----+-------+
|  1 | book1 |
|  2 | book2 |
|  3 | book3 |
|  4 | book4 |
+----+-------+
4 rows in set (0.00 sec)

mysql> select * from author;
+----+---------+
| ID | NAME    |
+----+---------+
|  1 | author1 |
|  2 | author2 |
|  3 | author1 |
|  4 | author2 |
|  5 | author3 |
|  6 | author2 |
|  7 | author3 |
|  8 | author3 |
+----+---------+
8 rows in set (0.00 sec)
    

My Expectation is to see tables like below:

mysql> select * from book_author;
+----------+------------+
| books_ID | authors_ID |
+----------+------------+
|        1 |          1 |
|        1 |          2 |
|        2 |          1 |
|        2 |          2 |
|        2 |          3 |
|        3 |          2 |
|        3 |          3 |
|        4 |          3 |
+----------+------------+


mysql> select * from book;
+----+-------+
| ID | NAME  |
+----+-------+
|  1 | book1 |
|  2 | book2 |
|  3 | book3 |
|  4 | book4 |
+----+-------+

mysql> select * from author;
+----+---------+
| ID | NAME    |
+----+---------+
|  1 | author1 |
|  2 | author2 |
|  3 | author3 |
+----+---------+


DBOperation.class

 public Object save(Object o) {
    Session session = HibernateUtil.getSessionFactory().openSession();
    Transaction transaction = session.beginTransaction();
    Object merge = null;
    try {
        merge = session.merge(o);
        transaction.commit();
    } catch (Exception e) {
        e.printStackTrace();
        transaction.rollback();
    } finally {
        session.close();
    }
    return merge;
}

Upvotes: 1

Views: 146

Answers (2)

SternK
SternK

Reputation: 13041

  1. You use bidirectional @ManyToMany association, so as hibernate documentation suggests, both sides of this association should be synchronized. It’s good practice to provide helper methods for adding or removing child entities.
@Entity
@Table(name = "BOOK")
public class Book {

   @SequenceGenerator(name = "SEQ_GEN_BOOK", allocationSize = 1, sequenceName = "TABLE_SEQ_GEN_BOOK", initialValue = 1)
   @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_GEN_BOOK")
   @Id
   @Column(name = "ID")
   private long id;

   @Column(name = "NAME", nullable = false, length = 100)
   private String name;

   // do not use raw types
   // see for example this https://stackoverflow.com/questions/2770321
   @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
   private Set<Author> authors = new HashSet<>();

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

   public void removeAddress(Author author) {
      authors.remove( author );
      author.getBooks().remove( this );
   }
   
   @Override
   public boolean equals(Object o) {
      // As you use Set collections for Author and Book
      // you should override equals/hashCode
      // and these implementation should be based on @NaturalIds
      // not on @Ids
   }
   
   @Override
   public int hashCode() {
      // ...
   }
}
  1. For new entities you should use persist(entity), merge(entity) should be used, to put entity back to persistence context if the entity was detached and was changed. See this.

So, you should do something like this:

Author author1 = new Author();
author1.setName("author1");
Author author2 = new Author();
author2.setName("author2");
Author author3 = new Author();
author3.setName("author3");

Book book1 = new Book();
book1.setName("book1");
Book book2 = new Book();
book2.setName("book2");
Book book3 = new Book();
book3.setName("book3");
Book book4 = new Book();
book4.setName("book4");

book1.addAuthor(author1);
book1.addAuthor(author2);

book2.addAuthor(author1);
book2.addAuthor(author2);
book2.addAuthor(author3);

book3.addAuthor(author2);
book3.addAuthor(author3);

book4.addAuthor(author3);

Session session = HibernateUtil.getSessionFactory().openSession();
Transaction transaction = session.beginTransaction();

session.persist(book1);
session.persist(book2);
session.persist(book3);
session.persist(book4);

transaction.commit();
session.close();

Upvotes: 1

Okay Atalay
Okay Atalay

Reputation: 111

I was creating different sessions for each save method call and i was calling session.merge(). I have changed DbOperations.getInstance().save(..) method content to insert all given rows in the same session by calling session.save() instead of session.merge(). See updated method below and it works well now.

public void save(Object... obj) {
        Session session = HibernateUtil.getSessionFactory().openSession();
        Transaction transaction = session.beginTransaction();
        try {
            for (Object o: obj) {
                session.save(o);
            }
            transaction.commit();
        } catch (Exception e) {
            e.printStackTrace();
            transaction.rollback();
        } finally {
            session.close();
        }
    }

Upvotes: 0

Related Questions