Many-to-many relationships occur when each record in an entity may have many linked records in another entity and vice-versa.

“Many-to-many” example

This is a many-to-many relationship table design, a STOCK table has more than one CATEGORY, and CATEGORY can belong to more than one STOCK, the relationship is linked with a third table called STOCK_CATEGORY.

many-to-many-relationship

Hibernate implementation

In Hibernate, this “many-to-many” relationship can implemented in two ways :

  • Hibernate XML Mapping file
  • Hibernate Annotation

1. Hibernate XML mapping file

In XML mapping way, you need three Java classes to hold the table data,

  • Stock.java -> STOCK table
  • Category.java -> CATEGORY table
  • StockCategory.java -> STOCK_CATEGORY table

and three XML mapping files to describe the table relationship.

  • Stock.hbm.xml -> STOCK table
  • Category.hbm.xml -> CATEGORY table
  • StockCategory.hbm.xml -> STOCK_CATEGORY table
Stock.java
public class Stock implements java.io.Serializable {
   private Set<StockCategory> stockCategories = new HashSet<StockCategory>(0);
   ...
   public Set<StockCategory> getStockCategories() {
	return this.stockCategories;
   }
 
   public void setStockCategories(Set<StockCategory> stockCategories) {
	this.stockCategories = stockCategories;
   }
Category.java
public class Category implements java.io.Serializable {
   private Set<StockCategory> stockCategories = new HashSet<StockCategory>(0);
   ...
   public Set<StockCategory> getStockCategories() {
	return this.stockCategories;
   }
 
   public void setStockCategories(Set<StockCategory> stockCategories) {
	this.stockCategories = stockCategories;
   }
StockCategory.java
public class StockCategory implements java.io.Serializable {
   private Integer stockCategoryId;
   private Stock stock;
   private Category category;
   ...
Stock.hbm.xml
...
<class name="com.mkyong.common.Stock" table="stock" ...>
   <id name="stockId" type="java.lang.Integer">
      <column name="STOCK_ID" />
      <generator class="identity" />
   </id>
   ...
   <set name="stockCategories" inverse="true" lazy="true" 
       table="stock_category" ...>
       <key>
         <column name="STOCK_ID" not-null="true" />
       </key>
          <one-to-many class="com.mkyong.common.StockCategory" />
   </set>
...

Set inverse=”true” in set variable, Stock does not want to maintain the relationship.

Category.hbm.xml
...
<class name="com.mkyong.common.Category" table="category" ...>
   <id name="categoryId" type="java.lang.Integer">
       <column name="CATEGORY_ID" />
       <generator class="identity" />
   </id>
   ...
   <set name="stockCategories" inverse="true" lazy="true" 
       table="stock_category" ...>
      <key>
         <column name="CATEGORY_ID" not-null="true" />
      </key>
         <one-to-many class="com.mkyong.common.StockCategory" />
   </set>
...

Set inverse=”true” in set variable, Category does not want to maintain the relationship also.

StockCategory.hbm.xml
...
<class name="com.mkyong.common.StockCategory" table="stock_category" ...>
   <id name="stockCategoryId" type="java.lang.Integer">
      <column name="STOCK_CATEGORY_ID" />
      <generator class="identity" />
   </id>
   <many-to-one name="stock" class="com.mkyong.common.Stock" ...>
       <column name="STOCK_ID" not-null="true" />
   </many-to-one>
   <many-to-one name="category" class="com.mkyong.common.Category" ...>
        <column name="CATEGORY_ID" not-null="true" />
    </many-to-one>
</class>
...

StockCategory maintain the relationship between Stock and Category, see inverse=”true” explanation.

2. Hibernate annotation

In annotation way, three classes are required, all the relationships are declared inside the Java classes.

  • Stock.java -> STOCK table
  • category.java -> CATEGORY table
  • StockCategory.java -> STOCK_CATEGORY table
Stock.java
...
@Entity
@Table(name = "stock", catalog = "mkyong", uniqueConstraints = {
		@UniqueConstraint(columnNames = "STOCK_NAME"),
		@UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {
	private Set<StockCategory> stockCategories = 
                                         new HashSet<StockCategory>(0);
	...
	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "STOCK_ID", unique = true, nullable = false)
	public Integer getStockId() {
		return this.stockId;
	}
        ...
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "stock")
	public Set<StockCategory> getStockCategories() {
		return this.stockCategories;
	}
 
	public void setStockCategories(Set<StockCategory> stockCategories) {
		this.stockCategories = stockCategories;
	}
...
Category.java
...
@Entity
@Table(name = "category", catalog = "mkyong")
public class Category implements java.io.Serializable {
	private Set<StockCategory> stockCategories = 
                                       new HashSet<StockCategory>(0);
        ...
	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "CATEGORY_ID", unique = true, nullable = false)
	public Integer getCategoryId() {
		return this.categoryId;
	}
	...
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "category")
	public Set<StockCategory> getStockCategories() {
		return this.stockCategories;
	}
 
	public void setStockCategories(Set<StockCategory> stockCategories) {
		this.stockCategories = stockCategories;
	}
...
StockCategory.java
@Entity
@Table(name = "stock_category", catalog = "mkyong", 
        uniqueConstraints = @UniqueConstraint(columnNames = {
        "STOCK_ID", "CATEGORY_ID" }))
public class StockCategory implements java.io.Serializable {
	private Integer stockCategoryId;
	private Stock stock;
	private Category category;
	...
	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "STOCK_CATEGORY_ID", unique = true, nullable = false)
	public Integer getStockCategoryId() {
		return this.stockCategoryId;
	}
 
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "STOCK_ID", nullable = false)
	public Stock getStock() {
		return this.stock;
	}
 
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "CATEGORY_ID", nullable = false)
	public Category getCategory() {
		return this.category;
	}
...

The code is self-explanatory, all the @annotation are the XML mapping tag substitution.

Test It

Run the example, both annotation and XML mapping file will generate the same output. Hibernate inserts a row into the STOCK table, a row into the CATEGORY table, and both relationship in STOCK_CATEGORY table.

        Stock stock = new Stock(); 
        Category category = new Category();
        ...
        session.save(stock);
        session.save(category);
        ...
        StockCategory stockCategory = new StockCategory(stock, category);
        session.save(stockCategory);
Output
Hibernate: 
    insert into mkyong.stock
    (STOCK_CODE, STOCK_NAME) 
    values (?, ?)
 
Hibernate: 
    insert into mkyong.category
    (`DESC`, NAME) 
    values (?, ?)
 
Hibernate: 
    insert into mkyong.stock_category
    (CATEGORY_ID, STOCK_ID) 
    values (?, ?)

Download Full Project

In this tutorial, many files and configuration steps are hidden, only the important files are remark. If you are interest to do the hand-on, you can download the full project here.

These two projects are developed in Eclipse IDE, using Maven as project management tool, and MySQL as database engine. The MySQL table creation scripts are stored in “db” folder.