Hibernate – Many-to-Many example (Annotation)

In this tutorial, it will reuse the entire infrastructure of the previous “Hibernate many to many example – XML mapping” tutorial, enhance it to support Hibernare / JPA annotation.

Note
For many to many with extra columns in join table, please refer to this tutorial.

Project Structure

Review the new project structure of this tutorial.

many to many project folder

1. “Many-to-many” table relationship

See the previous many to many table relationship again.

many to many ER diagram

2. Hibernate Model Class

Update previous model classes – Stock.java and Category.java, and define new annotation code inside.

File : Stock.java


package com.mkyong.stock;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.CascadeType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;

@Entity
@Table(name = "stock", catalog = "mkyongdb", uniqueConstraints = {
		@UniqueConstraint(columnNames = "STOCK_NAME"),
		@UniqueConstraint(columnNames = "STOCK_CODE") })
public class Stock implements java.io.Serializable {

	private Integer stockId;
	private String stockCode;
	private String stockName;
	private Set<Category> categories = new HashSet<Category>(0);

	public Stock() {
	}

	public Stock(String stockCode, String stockName) {
		this.stockCode = stockCode;
		this.stockName = stockName;
	}

	public Stock(String stockCode, String stockName, Set<Category> categories) {
		this.stockCode = stockCode;
		this.stockName = stockName;
		this.categories = categories;
	}

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "STOCK_ID", unique = true, nullable = false)
	public Integer getStockId() {
		return this.stockId;
	}

	public void setStockId(Integer stockId) {
		this.stockId = stockId;
	}

	@Column(name = "STOCK_CODE", unique = true, nullable = false, length = 10)
	public String getStockCode() {
		return this.stockCode;
	}

	public void setStockCode(String stockCode) {
		this.stockCode = stockCode;
	}

	@Column(name = "STOCK_NAME", unique = true, nullable = false, length = 20)
	public String getStockName() {
		return this.stockName;
	}

	public void setStockName(String stockName) {
		this.stockName = stockName;
	}

	@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
	@JoinTable(name = "stock_category", catalog = "mkyongdb", joinColumns = { 
			@JoinColumn(name = "STOCK_ID", nullable = false, updatable = false) }, 
			inverseJoinColumns = { @JoinColumn(name = "CATEGORY_ID", 
					nullable = false, updatable = false) })
	public Set<Category> getCategories() {
		return this.categories;
	}

	public void setCategories(Set<Category> categories) {
		this.categories = categories;
	}

}

File : Category.java


package com.mkyong.stock;

import java.util.HashSet;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import static javax.persistence.GenerationType.IDENTITY;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

@Entity
@Table(name = "category", catalog = "mkyongdb")
public class Category implements java.io.Serializable {

	private Integer categoryId;
	private String name;
	private String desc;
	private Set<Stock> stocks = new HashSet<Stock>(0);

	public Category() {
	}

	public Category(String name, String desc) {
		this.name = name;
		this.desc = desc;
	}

	public Category(String name, String desc, Set<Stock> stocks) {
		this.name = name;
		this.desc = desc;
		this.stocks = stocks;
	}

	@Id
	@GeneratedValue(strategy = IDENTITY)
	@Column(name = "CATEGORY_ID", unique = true, nullable = false)
	public Integer getCategoryId() {
		return this.categoryId;
	}

	public void setCategoryId(Integer categoryId) {
		this.categoryId = categoryId;
	}

	@Column(name = "NAME", nullable = false, length = 10)
	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Column(name = "[DESC]", nullable = false)
	public String getDesc() {
		return this.desc;
	}

	public void setDesc(String desc) {
		this.desc = desc;
	}

	@ManyToMany(fetch = FetchType.LAZY, mappedBy = "categories")
	public Set<Stock> getStocks() {
		return this.stocks;
	}

	public void setStocks(Set<Stock> stocks) {
		this.stocks = stocks;
	}

}

3. Hibernate Configuration File

Puts annotated classes Stock.java and Category.java in hibernate.cfg.xml like this :

File : hibernate.cfg.xml


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>
    <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
    <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/mkyongdb</property>
    <property name="hibernate.connection.username">root</property>
    <property name="hibernate.connection.password">password</property>
    <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
    <property name="show_sql">true</property>
    <mapping class="com.mkyong.stock.Stock" />
    <mapping class="com.mkyong.stock.Category" />
</session-factory>
</hibernate-configuration>

4. Run It

Run it, the result is self-explanatory.

File : App.java


package com.mkyong;

import java.util.HashSet;
import java.util.Set;
import org.hibernate.Session;
import com.mkyong.stock.Category;
import com.mkyong.stock.Stock;
import com.mkyong.util.HibernateUtil;

public class App {
	public static void main(String[] args) {
		
        System.out.println("Hibernate many to many (Annotation)");
	Session session = HibernateUtil.getSessionFactory().openSession();

	session.beginTransaction();

	Stock stock = new Stock();
        stock.setStockCode("7052");
        stock.setStockName("PADINI");
 
        Category category1 = new Category("CONSUMER", "CONSUMER COMPANY");
        Category category2 = new Category("INVESTMENT", "INVESTMENT COMPANY");
    
        Set<Category> categories = new HashSet<Category>();
        categories.add(category1);
        categories.add(category2);
        
        stock.setCategories(categories);
        
        session.save(stock);
    
	session.getTransaction().commit();
	System.out.println("Done");
	}
}

Output


Hibernate many to many (Annotation)
Hibernate: 
    insert 
    into
        mkyongdb.stock
        (STOCK_CODE, STOCK_NAME) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        mkyongdb.category
        (`DESC`, NAME) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        mkyongdb.category
        (`DESC`, NAME) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        mkyongdb.stock_category
        (STOCK_ID, CATEGORY_ID) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        mkyongdb.stock_category
        (STOCK_ID, CATEGORY_ID) 
    values
        (?, ?)
Done

Reference

  1. Hibernate Documentation – many to many relationship.

42 comments on “Hibernate – Many-to-Many example (Annotation)

  1. I’m getting a Duplicate entry ‘1’ for key ‘PRIMARY’ error. Probably in my database setup?

    Reply
  2. Hi Sir. Can I ask you a question? In your example, why do you must use table stock_categories to connect two table? Are annotation many to many need it? Can we use only two table : stock and categories to describe the relationship between them?
    Thank you and hope your answer.

    Reply
  3. Thank you verry much, this topic very helpful for me.

    Reply
  4. Can we save both way?

    Like first saving Stocks, then adding those to category and then save category.
    Another way, first save Categories, then adding those to stock and then save stock.

    In my case, only second is working. In first, join table is not updated.

    Reply
  5. Why is that the @JoinTable on the Stock side? Can I do the reverse:

    Category:

    @ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinTable(name = “stock_category”, catalog = “mkyongdb”, joinColumns = { @JoinColumn(name = “CATEGORY_ID”, nullable = false, updatable = false) }, inverseJoinColumns = { @JoinColumn(name = “STOCK_ID”, nullable = false, updatable = false) })
    public Set getStocks() {
    return this.stocks;
    }

    Stock:

    @ManyToMany(fetch = FetchType.LAZY, mappedBy = “stocks”)

    public Set getCategories() {
    return this.categories;
    }

    Reply
  6. I think, you should also be providing details how to create table schema details?

    Reply
  7. Hi,
    I decided to change from Collection to Set in my project, but I am getting NullPointerException after I load the entity with the fetched Set. And it works with Collection. Do you have idea how can I solve this? Thanks in advance.

    Reply
  8. Please add import statements in model classes to import other model class,….because in many-to-many relation it will be easy to get things clear by looking at import statements in model classes.

    Reply
  9. A hibernate syntax question, if you want to show all stocks or categories what would be the hibernate query?

    Reply
  10. HI Mykong,
    Can you please explain me How it is working as many to many association.Because it one stock can have many category.But i cannot do vice versa.

    Reply
  11. i always get “No converter found capable of converting from type java.lang.String to type @javax.persistence.OneToMany java.util.Set”

    where can i find an example / tutorial for a form with checboxes of a many-to-many relationship?

    Reply
  12. I have a problem with this this is the error

    Could not determine type for: java.util.List, at table: categories, for columns: [org.hibernate.mapping.Column(stocks)]

    I have the same code that you have but I don’t know what is missing

    Best Regards

    Reply
    1. It has been a while. The problem is because List was not instantiated as any concrete type, for example ArrayList() or LinkedList(). However now in the example, it is created as a Set and indeed it is instantiated as HashSet();

      Reply
  13. It is clear how to annotate two tables with one join tabke, and how to annotate three tables with one join table?
    For example: Students, Groups, Subjects with one join table Student_Group_Subject

    Reply
  14. I wrote my code like it is in example and I got error ” org.hibernate.hql.internal.ast.QuerySyntaxException: AcadProgramSubjectWithJoin is not mapped [from AcadProgramSubjectWithJoin where programId = ?1 and isActive = ?2] ” . Can anyone help me ?

    Reply
  15. What does inverseJoinColumns annotation does in STOCK table ?
    Is it compulsary to have primary key foreign key relationship between Stock,Stock_catgory and Category table? Please reply.

    Reply
  16. Hello mkyong.

    Is there any benefits of using @UniqueConstrains on table level AND then setting property unique=true at column level?
    Speciffication says:
    “Use the @UniqueConstraint annotation to specify that a unique constraint is to be included in the generated DDL for a primary or secondary table. Alternatively, you can specify unique constraints at the column level (see @Column).”
    So one might assume that you do not need both of them.

    Reply
  17. Thank excellent example.
    I have a question.

    I have three entities (stock, category and stock_category) and these are stored as shown in the example, but I want to store only two entities (stock and stock_category) that already has data category.

    How to insert only two entities (stock and stock_category)?

    Thanks again

    Reply
    1. Sorry for the inconvenience and found the solution to my problem:

      Stock stock = new Stock();
      stock.setStockCode(“7053”);
      stock.setStockName(“OTHER PADINI”);
      /* INICIO
      Español: En esta seccion se emula una consulta a la base de datos y se recupera un registro de categoria.

      English: In this section emulates a query to the database and retrieves a category record
      */
      Category category1 = new Category();
      category1.setCategoryId(1);
      category1.setName(“CONSUMER”);
      category1.setDesc(“CONSUMER COMPANY”);
      /*
      FIN de consulta
      END query
      */

      Set categories = new HashSet();
      categories.add(category1);

      stock.setCategories(categories);

      session.save(stock);

      Reply
  18. Troubles when i merge the first entity!!

    I have two entities (Contract,Book) mapped with the same annotations @ManytoMany with a List instead a set, and the fetch of de firts entity (Contract) is eager because i have to show for one contract his BookList.
    I have one entity ContractBook mapped to the table contract_book but i dont use that for nothing

    the problem is when i edit (merge)the first entity (Contract) takes 1 minute to record.
    when i see the logs of hibernate i have next the query of table contract_book, repeates 18506 times (the number of record of de first entity contract)

    What is wrong??
    Thanks

    SELECT listasubli0_.contrato as contrato24_5_, listasubli0_.libro as libro5_,
    libro1_.codlibro as codlibro29_0_, libro1_.cantidadlibro as cantidad2_29_0_, libro1_.categoria as categoria29_0_, libro1_.editorial as editorial29_0_, libro1_.estadolibro as estadoli3_29_0_, libro1_.nomlibro as nomlibro29_0_, libro1_.preciolibro as precioli5_29_0_, libro1_.sucursal as sucursal29_0_,
    categoria2_.codcategoria as codcateg1_31_1_, categoria2_.estado as estado31_1_, categoria2_.nomcategoria as nomcateg3_31_1_,
    editorial3_.codeditorial as codedito1_28_2_, editorial3_.celeditorial as celedito2_28_2_, editorial3_.direditorial as diredito3_28_2_, editorial3_.estado as estado28_2_, editorial3_.nomeditorial as nomedito5_28_2_, editorial3_.suceditorial as sucedito7_28_2_, editorial3_.tlfeditorial as tlfedito6_28_2_,
    sucursal4_.codsucursal as codsucur1_32_3_, sucursal4_.estado as estado32_3_, sucursal4_.nomsucursal as nomsucur3_32_3_,
    sucursal5_.codsucursal as codsucur1_32_4_, sucursal5_.estado as estado32_4_, sucursal5_.nomsucursal as nomsucur3_32_4_
    FROM contratolibro listasubli0_
    inner join libro libro1_ on listasubli0_.libro=libro1_.codlibro
    inner join categoria categoria2_ on libro1_.categoria=categoria2_.codcategoria
    inner join editorial editorial3_ on libro1_.editorial=editorial3_.codeditorial
    left outer join sucursal sucursal4_ on editorial3_.suceditorial=sucursal4_.codsucursal
    inner join sucursal sucursal5_ on libro1_.sucursal=sucursal5_.codsucursal
    WHERE listasubli0_.contrato=?

    Reply
  19. I used to be more than happy to seek out this web-site.I wanted to thanks on your time for this glorious learn!! I undoubtedly enjoying every little bit of it and I have you bookmarked to check out new stuff you weblog post.

    Reply
  20. How can I update the tables stock and category in the above example?
    I have one project with same details and I am not able to update the two tables.

    Reply
  21. Hi Mkyong,

    What if I have to do in case of deleting a single record from stock_category? How do I will delete the stock entry with the specific record ID?

    Regards,
    Usman

    Reply
    1. First, in real world the Category should be a domain object so you should use CascadeType.REFRESH instead of CascadeType.ALL.

      Reply
    2. Stock stock = em.find(Stock.class, 1);
      Set<Category> categories = new HashSet<Category>();
      stock.setCategories(categories);
      em.merge(stock);
      
      Reply
  22. What if I dont want to save the categories?

    Lets say categories is a domain table and it’s records are already saved. I just want to save stock and the association table (stock_category).

    What should I do?

    Reply
    1. You need to change CascadeType.ALL to CascadeType.REFRESH.

      Reply
  23. Hi, I couldnt find any sample code neither in hibernate references nor community about how to make this @ManyToMany association “unidirectional”. Hibernate doesnt persist to the join table when you set a stock list to a category instance for this example.

    And other question in my mind, why this manytomany solutions are considered poorly designed for databases. What other solutions can be done in situations like these.
    Thanks. Ahmet BAYIRLI

    Reply
    1. For unidirectional remove stocks variable from Category.

      Remove this, and all the associated methods.

      private Set<Stock> stocks = new HashSet<Stock>(0);
      
      Reply
  24. I am getting the below error on executing the code.

    Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not insert: [com.test.entities.CategoryEntity]
    	at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:92)
    	at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:66)
    	at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:64)
    	at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2345)
    	at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2852)
    	at org.hibernate.action.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:71)
    	at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
    	at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:320)
    	at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203)
    	at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:129)
    	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:117)
    	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    	at org.hibernate.impl.SessionImpl.fireSaveOrUpdate(SessionImpl.java:685)
    	at org.hibernate.impl.SessionImpl.saveOrUpdate(SessionImpl.java:677)
    	at org.hibernate.engine.CascadingAction$5.cascade(CascadingAction.java:252)
    	at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:392)
    	at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:335)
    	at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
    	at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:425)
    	at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:362)
    	at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:338)
    	at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:204)
    	at org.hibernate.engine.Cascade.cascade(Cascade.java:161)
    	at org.hibernate.event.def.AbstractSaveEventListener.cascadeAfterSave(AbstractSaveEventListener.java:475)
    	at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:353)
    	at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:203)
    	at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:129)
    	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:210)
    	at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:56)
    	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:195)
    	at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:50)
    	at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:93)
    	at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:713)
    	at org.hibernate.impl.SessionImpl.save(SessionImpl.java:701)
    	at org.hibernate.impl.SessionImpl.save(SessionImpl.java:697)
    	at com.test.App.main(App.java:74)
    	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    	at java.lang.reflect.Method.invoke(Method.java:597)
    	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:115)
    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'DESC, NAME) values ('CC2', 'C2')' at line 1
    	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    	at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    	at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
    	at com.mysql.jdbc.Util.getInstance(Util.java:381)
    	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1030)
    	at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)
    	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3558)
    	at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3490)
    	at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1959)
    	at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2109)
    	at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2643)
    	at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:2077)
    	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2362)
    	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2280)
    	at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2265)
    	at org.hibernate.id.IdentityGenerator$GetGeneratedKeysDelegate.executeAndExtract(IdentityGenerator.java:94)
    	at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:57)
    	... 40 more
    
    Reply
  25. Thanks for Example, it is very detailed. I have one question,
    If i delete a stock:
    1) record from Stock table will be deleted
    2) record from stock_category table will be deleted
    3) record from category table will be deleted as well

    But i don’t want to delete category from category table(step 3), how can we achieve that? Do we need to map something like in https://mkyong.com/hibernate/hibernate-many-to-many-example-join-table-extra-column-annotation/?

    Reply
  26. Hi,

    In the case of you i to delete Stock with code = 7052 and all their association in the table stock_category (without delete de category record’s), what’s i must do?

    Ex: categoryDAO.delete(stock);

    Reply

Leave a Comment

Your email address will not be published. Required fields are marked *