Hibernate Many To Many 매핑 – 조인 테이블

오늘은 XML과 주석 구성을 사용하여 하이버네이트 Many to Many 매핑을 살펴보겠습니다. 이전에는 하이버네이트에서 One To OneOne To Many 매핑을 구현하는 방법을 살펴보았습니다.

하이버네이트 Many to Many

Many-to-Many 매핑은 일반적으로 데이터베이스에서 Join Table을 사용하여 구현됩니다. 예를 들어, 우리는 CartItem 테이블 및 많은-to-many 매핑을 위한 Cart_Items 테이블을 가질 수 있습니다. 모든 카트는 여러 개의 항목을 가질 수 있으며, 모든 항목은 여러 개의 카트에 포함될 수 있으므로, 여기에는 많은-to-many 매핑이 있습니다.

하이버네이트 Many to Many 매핑 데이터베이스 설정

아래 스크립트는 많은-to-many 예제 데이터베이스 테이블을 생성하는 데 사용할 수 있으며, 이 스크립트는 MySQL 데이터베이스를 위한 것입니다. 다른 데이터베이스를 사용하는 경우 작은 변경이 필요할 수 있습니다.

DROP TABLE IF EXISTS `Cart_Items`;
DROP TABLE IF EXISTS `Cart`;
DROP TABLE IF EXISTS `Item`;

CREATE TABLE `Cart` (
  `cart_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `cart_total` decimal(10,0) NOT NULL,
  PRIMARY KEY (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

CREATE TABLE `Item` (
  `item_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `item_desc` varchar(20) NOT NULL,
  `item_price` decimal(10,0) NOT NULL,
  PRIMARY KEY (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `Cart_Items` (
  `cart_id` int(11) unsigned NOT NULL,
  `item_id` int(11) unsigned NOT NULL,
  PRIMARY KEY (`cart_id`,`item_id`),
  CONSTRAINT `fk_cart` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`),
  CONSTRAINT `fk_item` FOREIGN KEY (`item_id`) REFERENCES `Item` (`item_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

알림: Cart_Items 테이블에는 추가 열이 없습니다. 사실, 다대다 매핑 테이블에 추가 열이 있는 것은 큰 의미가 없습니다. 그러나 추가 열이 있는 경우 구현이 약간 변경되며, 이에 대해서는 다른 게시물에서 살펴볼 것입니다. 아래 다이어그램은 이러한 테이블 간의 엔터티 관계를 보여줍니다. 이제 데이터베이스 설정이 준비되었으니, 하이버네이트 다대다 매핑 프로젝트를 생성해 보겠습니다.

하이버네이트 다대다 매핑 프로젝트 구조

Eclipse 또는 좋아하는 IDE에서 메이븐 프로젝트를 만들어 보세요. 아래 이미지는 응용 프로그램의 구조와 다른 구성 요소를 보여줍니다. 먼저 XML 기반 매핑 구현을 살펴본 다음 JPA 주석을 사용하는 방법을 살펴보겠습니다.

하이버네이트 메이븐 종속성

우리 최종 pom.xml은 최신 버전인 Hibernate 종속성 4.3.5.Final과 mysql 드라이버 종속성을 포함하고 있습니다. pom.xml

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.journaldev.hibernate</groupId>
	<artifactId>HibernateManyToManyMapping</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<dependencies>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-core</artifactId>
			<version>4.3.5.Final</version>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.0.5</version>
		</dependency>
	</dependencies>

</project>

Hibernate Many to Many XML Configuration Model Classes

Cart.java

package com.journaldev.hibernate.model;

import java.util.Set;

public class Cart {

	private long id;
	private double total;

	private Set<Item> items;
	
	public double getTotal() {
		return total;
	}

	public void setTotal(double total) {
		this.total = total;
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public Set<Item> getItems() {
		return items;
	}

	public void setItems(Set<Item> items) {
		this.items = items;
	}

}

Item.java

package com.journaldev.hibernate.model;

import java.util.Set;

public class Item {

	private long id;
	private double price;
	private String description;

	private Set<Cart> carts;
	
	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double price) {
		this.price = price;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Set<Cart> getCarts() {
		return carts;
	}

	public void setCarts(Set<Cart> carts) {
		this.carts = carts;
	}
}

Cart는 Item의 집합을 가지고 있으며, Item은 Cart의 집합을 가지고 있습니다. 이렇게 하면 양방향 연관 관계를 구현할 수 있습니다. 즉, Cart를 저장할 때 Item을 저장할 수 있고, 그 반대로도 가능합니다. 단방향 매핑의 경우, 일반적으로 하나의 모델 클래스에 설정이 있습니다. 단방향 매핑에는 주로 어노테이션을 사용합니다.

Hibernate Many To Many Mapping XML Configuration

Cart와 Item에 대한 hibernate many to many 매핑 xml 구성 파일을 만들어 보겠습니다. 우리는 양방향 매핑을 구현할 것입니다. cart.hbm.xml

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
	"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.journaldev.hibernate.model">
	<class name="Cart" table="CART">
		<id name="id" type="long">
			<column name="cart_id" />
			<generator class="identity" />
		</id>
		<property name="total" type="double" column="cart_total" />

		<set name="items" table="CART_ITEMS" fetch="select" cascade="all">
			<key column="cart_id" />
			<many-to-many class="Item" column="item_id" />
		</set>
	</class>

</hibernate-mapping>

Item의 집합은 CART_ITEMS 테이블에 매핑됩니다. Cart가 주요 객체이므로 cart_id가 키이며, many-to-many 매핑은 Item 클래스의 item_id 열을 사용합니다. item.hbm.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" 
"https://hibernate.org/dtd/hibernate-mapping-3.0.dtd" >

<hibernate-mapping package="com.journaldev.hibernate.model">

	<class name="Item" table="ITEM">
		<id name="id" type="long">
			<column name="item_id" />
			<generator class="identity" />
		</id>
		<property name="description" type="string" column="item_desc" />

		<property name="price" type="double" column="item_price" />

		<set name="carts" table="CART_ITEMS" fetch="select" cascade="all">
			<key column="item_id" />
			<many-to-many class="Cart" column="cart_id" />
		</set>

	</class>

</hibernate-mapping>

위에서 볼 수 있듯이 매핑은 Cart 매핑 구성과 매우 유사합니다.

XML 기반 다대다 매핑을 위한 Hibernate 구성

저희 Hibernate 구성 파일은 아래와 같이 보입니다. hibernate.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"https://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.password">pankaj123</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
		<property name="hibernate.connection.username">pankaj</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

		<property name="hibernate.current_session_context_class">thread</property>
		<property name="hibernate.show_sql">true</property>

		<mapping resource="cart.hbm.xml" />
		<mapping resource="item.hbm.xml" />
	</session-factory>
</hibernate-configuration>

XML 기반 매핑을 위한 Hibernate SessionFactory 유틸리티 클래스

HibernateUtil.java

package com.journaldev.hibernate.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class HibernateUtil {

	private static SessionFactory sessionFactory;

	private static SessionFactory buildSessionFactory() {
		try {
			// hibernate.cfg.xml에서 SessionFactory 생성
			Configuration configuration = new Configuration();
			configuration.configure("hibernate.cfg.xml");
			System.out.println("Hibernate Configuration loaded");

			ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder()
					.applySettings(configuration.getProperties()).build();
			System.out.println("Hibernate serviceRegistry created");

			SessionFactory sessionFactory = configuration
					.buildSessionFactory(serviceRegistry);

			return sessionFactory;
		} catch (Throwable ex) {
			System.err.println("Initial SessionFactory creation failed." + ex);
			ex.printStackTrace();
			throw new ExceptionInInitializerError(ex);
		}
	}

	public static SessionFactory getSessionFactory() {
		if (sessionFactory == null)
			sessionFactory = buildSessionFactory();
		return sessionFactory;
	}

}

이것은 SessionFactory의 팩토리로 작동하는 간단한 유틸리티 클래스입니다.

다대다 매핑 XML 구성 테스트 프로그램

우리의 하이버네이트 매니 투 매니 매핑 설정이 준비되었습니다, 이제 테스트해 보겠습니다. 두 개의 프로그램을 작성할 것인데, 하나는 Cart를 저장하고 Item과 Cart_Items 정보가 함께 저장되는지 확인하는 것이고, 다른 하나는 Item 데이터를 저장하고 해당하는 Cart와 Cart_Items가 저장되는지 확인하는 것입니다. HibernateManyToManyMain.java

package com.journaldev.hibernate.main;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.journaldev.hibernate.model.Cart;
import com.journaldev.hibernate.model.Item;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernateManyToManyMain {
	
	//Cart가 기본인 매니 투 매니를 저장
	public static void main(String[] args) {
		
		Item iphone = new Item();
		iphone.setPrice(100); iphone.setDescription("iPhone");
		
		Item ipod = new Item();
		ipod.setPrice(50); ipod.setDescription("iPod");
		
		Set items = new HashSet();
		items.add(iphone); items.add(ipod);
		
		Cart cart = new Cart();
		cart.setItems(items);
		cart.setTotal(150);
		
		Cart cart1 = new Cart();
		Set items1 = new HashSet();
		items1.add(iphone);
		cart1.setItems(items1);
		cart1.setTotal(100);
		
		SessionFactory sessionFactory = null;
		try{
		sessionFactory = HibernateUtil.getSessionFactory();
		Session session = sessionFactory.getCurrentSession();
		Transaction tx = session.beginTransaction();
		session.save(cart);
		session.save(cart1);
		System.out.println("Before committing transaction");
		tx.commit();
		sessionFactory.close();
		
		System.out.println("Cart ID="+cart.getId());
		System.out.println("Cart1 ID="+cart1.getId());
		System.out.println("Item1 ID="+iphone.getId());
		System.out.println("Item2 ID="+ipod.getId());
		
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close();
		}
		
	}

}

위의 하이버네이트 매니 투 매니 매핑 예제 프로그램을 실행하면 다음과 같은 출력을 얻습니다.

Hibernate Configuration loaded
Hibernate serviceRegistry created
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
Before committing transaction
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Cart ID=1
Cart1 ID=2
Item1 ID=1
Item2 ID=2

첫 번째 Cart를 통해 Item 데이터가 저장되면 item_id가 생성되고, 두 번째 Cart를 저장할 때는 다시 저장되지 않습니다. 또 다른 중요한 점은 매니 투 매니 조인 테이블 데이터가 트랜잭션을 커밋할 때 저장된다는 것입니다. 이는 트랜잭션을 롤백할 경우 성능을 향상시키기 위해 수행됩니다. HibernateBiDirectionalManyToManyMain.java

package com.journaldev.hibernate.main;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.journaldev.hibernate.model.Cart;
import com.journaldev.hibernate.model.Item;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernateBiDirectionalManyToManyMain {

	//Item이 기본인 매니 투 매니를 저장
	public static void main(String[] args) {
		
		Item iphone = new Item();
		iphone.setPrice(100); iphone.setDescription("iPhone");
		
		Item ipod = new Item();
		ipod.setPrice(50); ipod.setDescription("iPod");
		
		Cart cart = new Cart();
		cart.setTotal(150);
		
		Cart cart1 = new Cart();
		cart1.setTotal(100);
		
		Set cartSet = new HashSet();
		cartSet.add(cart);cartSet.add(cart1);
		
		Set cartSet1 = new HashSet();
		cartSet1.add(cart);
		
		iphone.setCarts(cartSet1);
		ipod.setCarts(cartSet);
		
		SessionFactory sessionFactory = null;
		try{
		sessionFactory = HibernateUtil.getSessionFactory();
		Session session = sessionFactory.getCurrentSession();
		Transaction tx = session.beginTransaction();
		session.save(iphone);
		session.save(ipod);
		tx.commit();
		sessionFactory.close();
		
		System.out.println("Cart ID="+cart.getId());
		System.out.println("Cart1 ID="+cart1.getId());
		System.out.println("Item1 ID="+iphone.getId());
		System.out.println("Item2 ID="+ipod.getId());
		
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close();
		}
		
	}

}

위의 프로그램의 출력은 다음과 같습니다:

Hibernate Configuration loaded
Hibernate serviceRegistry created
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
Hibernate: insert into CART_ITEMS (item_id, cart_id) values (?, ?)
Cart ID=3
Cart1 ID=4
Item1 ID=3
Item2 ID=4

이전의 테스트 프로그램과 쉽게 관련짓을 수 있습니다. 양방향 매핑을 구성했기 때문에 Item이나 Cart를 저장하면 자동으로 매핑된 데이터가 저장됩니다.

Hibernate Many To Many Mapping Annotation

이제 우리는 하이버네이트 XML 구성을 사용하여 다대다 매핑을 구성하는 방법을 살펴 보았으니, 어노테이션을 통해 그것을 구현하는 예제를 살펴 보겠습니다. 우리는 JPA 어노테이션을 사용하여 일방향 다대다 매핑을 구현할 것입니다.

하이버네이트 구성 XML 파일

저희의 어노테이션 기반 하이버네이트 구성 파일은 아래와 같습니다. hibernate-annotation.cfg.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
		"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
		"https://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.password">pankaj123</property>
		<property name="hibernate.connection.url">jdbc:mysql://localhost/TestDB</property>
		<property name="hibernate.connection.username">pankaj</property>
		<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>

		<property name="hibernate.current_session_context_class">thread</property>
		<property name="hibernate.show_sql">true</property>

		<mapping class="com.journaldev.hibernate.model.Cart1" />
		<mapping class="com.journaldev.hibernate.model.Item1" />
	</session-factory>
</hibernate-configuration>

하이버네이트 SessionFactory 유틸리티 클래스

SessionFactory를 생성하기 위한 유틸리티 클래스는 아래와 같습니다. HibernateAnnotationUtil.java

package com.journaldev.hibernate.util;

import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;

public class HibernateAnnotationUtil {

	private static SessionFactory sessionFactory;
	
	private static SessionFactory buildSessionFactory() {
        try {
            // hibernate-annotation.cfg.xml에서 SessionFactory 생성
        	Configuration configuration = new Configuration();
        	configuration.configure("hibernate-annotation.cfg.xml");
        	System.out.println("Hibernate Annotation Configuration loaded");
        	
        	ServiceRegistry serviceRegistry = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties()).build();
        	System.out.println("Hibernate Annotation serviceRegistry created");
        	
        	SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
        	
            return sessionFactory;
        }
        catch (Throwable ex) {
            System.err.println("Initial SessionFactory creation failed." + ex);
            ex.printStackTrace();
            throw new ExceptionInInitializerError(ex);
        }
    }
	
	public static SessionFactory getSessionFactory() {
		if(sessionFactory == null) sessionFactory = buildSessionFactory();
        return sessionFactory;
    }
}

하이버네이트 다대다 매핑 어노테이션 모델 클래스

이것은 어노테이션 기반 매핑의 가장 중요한 부분입니다. 먼저 Item 테이블 모델 클래스를 살펴보고 나서 Cart 테이블 모델 클래스를 살펴 보겠습니다. Item1.java

package com.journaldev.hibernate.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="ITEM")
public class Item1 {

	@Id
	@Column(name="item_id")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private long id;
	
	@Column(name="item_price")
	private double price;
	
	@Column(name="item_desc")
	private String description;
	
// Getter Setter 메서드
}

Item1 클래스는 간단합니다. 여기에는 관계 매핑이 없습니다. Cart1.java

package com.journaldev.hibernate.model;

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;
import javax.persistence.Table;

@Entity
@Table(name = "CART")
public class Cart1 {

	@Id
	@Column(name = "cart_id")
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private long id;

	@Column(name = "cart_total")
	private double total;

	@ManyToMany(targetEntity = Item1.class, cascade = { CascadeType.ALL })
	@JoinTable(name = "CART_ITEMS", 
				joinColumns = { @JoinColumn(name = "cart_id") }, 
				inverseJoinColumns = { @JoinColumn(name = "item_id") })
	private Set items;

//Getter Setter 메서드
}

가장 중요한 부분은 ManyToMany 주석 및 JoinTable 주석의 사용입니다. 여기서는 많은-대-많은 매핑에 사용할 테이블 이름 및 열을 제공합니다.

하이버네이트 매니-투-매니 주석 매핑 테스트 프로그램

여기에는 하이버네이트 매니-투-매니 매핑 주석 기반 구성에 대한 간단한 테스트 프로그램이 있습니다. HibernateManyToManyAnnotationMain.java

package com.journaldev.hibernate.main;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

import com.journaldev.hibernate.model.Cart1;
import com.journaldev.hibernate.model.Item1;
import com.journaldev.hibernate.util.HibernateAnnotationUtil;

public class HibernateManyToManyAnnotationMain {

	public static void main(String[] args) {
		Item1 item1 = new Item1();
		item1.setDescription("samsung"); item1.setPrice(300);
		Item1 item2 = new Item1();
		item2.setDescription("nokia"); item2.setPrice(200);
		Cart1 cart = new Cart1();
		cart.setTotal(500);
		Set<Item1> items = new HashSet<Item1>();
		items.add(item1); items.add(item2);
		cart.setItems(items);
		
		SessionFactory sessionFactory = null;
		try{
		sessionFactory = HibernateAnnotationUtil.getSessionFactory();
		Session session = sessionFactory.getCurrentSession();
		Transaction tx = session.beginTransaction();
		session.save(cart);
		System.out.println("Before committing transaction");
		tx.commit();
		sessionFactory.close();
		
		System.out.println("Cart ID="+cart.getId());
		System.out.println("Item1 ID="+item1.getId());
		System.out.println("Item2 ID="+item2.getId());
		
		}catch(Exception e){
			e.printStackTrace();
		}finally{
			if(sessionFactory != null && !sessionFactory.isClosed()) sessionFactory.close();
		}
	}

}

위의 프로그램을 실행하면 다음 출력이 생성됩니다.

Hibernate Annotation Configuration loaded
Hibernate Annotation serviceRegistry created
Hibernate: insert into CART (cart_total) values (?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Hibernate: insert into ITEM (item_desc, item_price) values (?, ?)
Before committing transaction
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Hibernate: insert into CART_ITEMS (cart_id, item_id) values (?, ?)
Cart ID=5
Item1 ID=6
Item2 ID=5

카트를 저장하면 Item 및 Cart_Items 테이블에도 데이터가 저장됩니다. 아이템 정보만 저장하면 Cart 및 Cart_Items 데이터가 저장되지 않음을 알 수 있습니다. 이것이 Hibernate 매니-투-매니 매핑 예제 자습서입니다. 아래 링크에서 샘플 프로젝트를 다운로드하여 더 많이 배울 수 있습니다.

Hibernate ManyToMany 매핑 프로젝트 다운로드

Source:
https://www.digitalocean.com/community/tutorials/hibernate-many-to-many-mapping-join-tables