Hibernate @ManyToMany Unidirectional and Bidirectional

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.

Authors publish Books, and Books have Authors. Any one Author can publish many Books, and any one Book can be published by many Authors, 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 Authors into the Book objects. Make sure you don’t forget that we need to assign a Set of Authors 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 Books 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 Books and Authors from either object.

Knowing this, we assign a Set of Books 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 Sets that represent the books. We assign the appropriate Books into these new Sets and assign them to the Authors.

Now the only difference is that we save the Authors, as that’s how it’s done with the bidirectional relationships… the unidirectional relationships require that you save the child side via DAOs.