Hibernate @ManyToMany Unidirectional
The Many-to-Many relationship can be best described by example.
The example we're going to use is that of the relationship between an Author
and a Book
.
Author
s publish Book
s, and Book
s have Author
s. Any one Author
can publish many Book
s, and any one Book
can be published by many Author
s, so this is why it is a many to many relationship.
Other examples of the many to many relationship are Students to Courses and Employees to Projects.
Let's take a look at how the unidirectional many-to-many relationship is created using Hibernate:
Author.java
import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Author { private Long authorId; private String authorName; @Id @GeneratedValue(strategy=GenerationType.AUTO) public Long getAuthorId() { return authorId; } public void setAuthorId(Long authorId) { this.authorId = authorId; } @Column(name="author_name") public String getAuthorName() { return authorName; } public void setAuthorName(String authorName) { this.authorName = authorName; } }
Book.java
import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; @Entity public class Book { private Long bookId; private String bookName; private Set<Author> authors; @Id @GeneratedValue(strategy=GenerationType.AUTO) public Long getBookId() { return bookId; } public void setBookId(Long bookId) { this.bookId = bookId; } @Column(name="book_name") public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } @ManyToMany(cascade=CascadeType.ALL) @JoinTable(name="author_book", joinColumns=@JoinColumn(name="book_id"), inverseJoinColumns=@JoinColumn(name="author_id")) public Set<Author> getAuthors() { return authors; } public void setAuthors(Set<Author> authors) { this.authors = authors; } }
The only thing that's different from the other relationships we've already looked at in the past two posts is the use of the join table. A join table is necessary to facilitate the many-to-many relationship. Since we need to use this 3rd table in the database schema to map out this many to many relationship, we'll need to let Hibernate know how to construct this join table.
We first specify the name of the table “author_book”. The convention for naming the join table is to concatenate the two names of the tables that make up the relationship, hence “author_book”. The next thing we must do is specify the names of the foreign keys that will exist inside the table. These foreign keys are referred to as the join column and inverse join column. The join column in this case refers to the name of the foreign key that belongs to the object that we're currently coding… which in this case is the Book
object. So that's why we specify “book_id” as the join column. The inverse join column is just the name of the foreign key that belongs to the OTHER table, which is the Author
table, hence “author_id”.
With that code in place, we've now successfully mapped out our relationship! All that's left is to understand how to create the objects and assign the appropriate values to them.
Code to Persist Data
Set<Author> howToProgramWithJavaAuthor = new HashSet<Author>(); Set<Author> howToProgramWithJava2ndAuthors = new HashSet<Author>(); Set<Author> howToPlayGuitarAuthor = new HashSet<Author>(); Author author = new Author(); author.setAuthorName("Trevor Page"); howToProgramWithJavaAuthor.add(author); Author author2 = new Author(); author2.setAuthorName("John Doe"); howToProgramWithJava2ndAuthors.add(author); howToProgramWithJava2ndAuthors.add(author2); howToPlayGuitarAuthor.add(author2); Book book = new Book(); book.setBookName("How to Program with Java"); Book book2 = new Book(); book2.setBookName("How to Program with Java 2nd Edition"); Book book3 = new Book(); book3.setBookName("How to Play Guitar"); book.setAuthors(howToProgramWithJavaAuthor); book2.setAuthors(howToProgramWithJava2ndAuthors); book3.setAuthors(howToPlayGuitarAuthor); dao.save(book); dao.save(book2); dao.save(book3);
In the unidirectional relationship (just like with the other two types of relationships), we set the parent values into the children. So in this case we set the Author
s into the Book
objects. Make sure you don't forget that we need to assign a Set
of Author
s into the Book
objects.
We then save each Book
object via our standard DAO object and we're all set!
Hibernate @ManyToMany Bidirectional
Author.java
import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToMany; @Entity public class Author { private Long authorId; private String authorName; private Set<Book> books; @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="author_id") public Long getAuthorId() { return authorId; } public void setAuthorId(Long authorId) { this.authorId = authorId; } @Column(name="author_name") public String getAuthorName() { return authorName; } public void setAuthorName(String authorName) { this.authorName = authorName; } @ManyToMany(cascade=CascadeType.ALL, mappedBy="authors") public Set<Book> getBooks() { return books; } public void setBooks(Set<Book> books) { this.books = books; } }
The main difference between the bidirectional and unidirectional many to many relationship is the fact that we need to create the instance variable that will hold references to Book
s in the Author
object. This makes sense since we're creating the bidirectional many to many relationship, which means we should be able to access the objects that are related to both the Book
s and Author
s from either object.
Knowing this, we assign a Set
of Book
s to the Author
object.
On the getter method, we create the @ManyToMany
mapping with the key mappedBy
property to indicate that this is the parent side of the relationship (the owning side).
This is the case because we can have an Author
without a Book
object (I decided that an Author
can be in the process of writing a book, but hasn't yet published it, so an Author
can exist without a Book
).
Book.java
import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; @Entity public class Book { private Long bookId; private String bookName; private Set<Author> authors; @Id @GeneratedValue(strategy=GenerationType.AUTO) @Column(name="book_id") public Long getBookId() { return bookId; } public void setBookId(Long bookId) { this.bookId = bookId; } @Column(name="book_name") public String getBookName() { return bookName; } public void setBookName(String bookName) { this.bookName = bookName; } @ManyToMany(cascade=CascadeType.ALL) @JoinTable(name="author_book", joinColumns=@JoinColumn(name="book_id"), inverseJoinColumns=@JoinColumn(name="author_id")) public Set<Author> getAuthors() { return authors; } public void setAuthors(Set<Author> authors) { this.authors = authors; } }
The Book
class remains the same as in the unidirectional code above.
But the way we persist the objects will change:
Code to Persist Data
Set<Author> howToProgramWithJavaAuthor = new HashSet<Author>(); Set<Author> howToProgramWithJava2ndAuthors = new HashSet<Author>(); Set<Author> howToPlayGuitarAuthor = new HashSet<Author>(); Set<Book> trevorsBooks = new HashSet<Book>(); Set<Book> johnsBooks = new HashSet<Book>(); Author author = new Author(); author.setAuthorName("Trevor Page"); howToProgramWithJavaAuthor.add(author); Author author2 = new Author(); author2.setAuthorName("John Doe"); howToProgramWithJava2ndAuthors.add(author); howToProgramWithJava2ndAuthors.add(author2); howToPlayGuitarAuthor.add(author2); Book book = new Book(); book.setBookName("How to Program with Java"); Book book2 = new Book(); book2.setBookName("How to Program with Java 2nd Edition"); Book book3 = new Book(); book3.setBookName("How to Play Guitar"); trevorsBooks.add(book); trevorsBooks.add(book2); johnsBooks.add(book2); johnsBooks.add(book3); author.setBooks(trevorsBooks); author2.setBooks(johnsBooks); book.setAuthors(howToProgramWithJavaAuthor); book2.setAuthors(howToProgramWithJava2ndAuthors); book3.setAuthors(howToPlayGuitarAuthor); dao.save(author); dao.save(author2);
This looks like a big jumble of code, but the only thing we've done is add the code to assign the other half of the relationship. This means that we need to now create Set
s that represent the books. We assign the appropriate Book
s into these new Set
s and assign them to the Author
s.
Now the only difference is that we save the Author
s, as that's how it's done with the bidirectional relationships… the unidirectional relationships require that you save the child side via DAOs.