하이버네이트 One To Many 매핑 예제 어노테이션

오늘은 Hibernate에서 일대다 매핑을 살펴보겠습니다. Annotation과 XML 구성을 사용한 Hibernate 일대다 매핑 예제를 살펴보겠습니다.

Hibernate에서 일대다 매핑

간단하게 말하면, 일대다 매핑은 한 테이블의 한 행이 다른 테이블의 여러 행과 매핑될 수 있다는 것을 의미합니다. 예를 들어, 항목을 위한 다른 테이블이 있는 카트 시스템을 생각해보세요. 카트에는 여러 항목이 있을 수 있으므로 여기서는 일대다 매핑이 있습니다. Hibernate 일대다 매핑 예제에는 카트-항목 시나리오를 사용할 것입니다.

Hibernate에서 일대다 매핑 – 데이터베이스 설정

일대다 매핑을 위해 외래 키 제약 조건을 사용할 수 있습니다. 아래는 CartItems 테이블에 대한 데이터베이스 스크립트입니다. Hibernate 일대다 매핑 예제에는 MySQL 데이터베이스를 사용하고 있습니다. setup.sql

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

CREATE TABLE `Items` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `cart_id` int(11) unsigned NOT NULL,
  `item_id` varchar(10) NOT NULL,
  `item_total` decimal(10,0) NOT NULL,
  `quantity` int(3) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `cart_id` (`cart_id`),
  CONSTRAINT `items_ibfk_1` FOREIGN KEY (`cart_id`) REFERENCES `Cart` (`cart_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

쇼핑 카트 및 상품 테이블의 ER 다이어그램이 아래에 나와 있습니다. 데이터베이스 설정이 준비되었습니다. 이제 하이버네이트 One to Many 매핑 예제 프로젝트로 넘어갑시다. 먼저 XML 기반 구성을 사용한 다음 하이버네이트와 JPA 어노테이션을 사용하여 일대다 매핑을 구현합니다.

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

Eclipse 또는 선호하는 IDE에서 간단한 Maven 프로젝트를 만들어 최종 프로젝트 구조가 아래 이미지처럼 보이도록 합니다.

하이버네이트 Maven 종속성

최종 pom.xml 파일에는 하이버네이트와 MySQL 드라이버에 대한 종속성이 포함되어 있습니다. 하이버네이트는 JBoss 로깅을 사용하며 자동으로 전이적 종속성으로 추가됩니다.

<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>HibernateOneToManyMapping</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>

참고로 나는 최신 하이버네이트 버전을 사용하고 있습니다. 4.3.5.Final 및 내 데이터베이스 설치에 기반한 MySQL 드라이버 버전입니다.

하이버네이트 원 대 다 매핑 모델 클래스

저희의 테이블 Cart와 Items에는 그에 해당하는 모델 클래스가 있습니다. Cart.java

package com.journaldev.hibernate.model;

import java.util.Set;

public class Cart {

	private long id;
	private double total;
	private String name;
	private Set<Items> items;
	
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	public double getTotal() {
		return total;
	}
	public void setTotal(double total) {
		this.total = total;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Set<Items> getItems() {
		return items;
	}
	public void setItems(Set<Items> items) {
		this.items = items;
	}
	
}

I am using Set of Items, so that every record is unique. We can also use List or Array for one to many mapping in hibernate. Items.java

package com.journaldev.hibernate.model;

public class Items {

	private long id;
	private String itemId;
	private double itemTotal;
	private int quantity;
	private Cart cart;
	
	// 하이버네이트는 인수가 없는 생성자를 필요로합니다
	public Items(){}
	
	public Items(String itemId, double total, int qty, Cart c){
		this.itemId=itemId;
		this.itemTotal=total;
		this.quantity=qty;
		this.cart=c;
	}
	public String getItemId() {
		return itemId;
	}
	public void setItemId(String itemId) {
		this.itemId = itemId;
	}
	public double getItemTotal() {
		return itemTotal;
	}
	public void setItemTotal(double itemTotal) {
		this.itemTotal = itemTotal;
	}
	public int getQuantity() {
		return quantity;
	}
	public void setQuantity(int quantity) {
		this.quantity = quantity;
	}
	public Cart getCart() {
		return cart;
	}
	public void setCart(Cart cart) {
		this.cart = cart;
	}
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
	
}

Items는 Cart와의 다 대 일 관계를 가지므로 Cart 객체에 대한 Collection이 필요하지 않습니다.

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

우리는 하이버네이트 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;
    }
}

하이버네이트 구성 XML 파일

우리의 하이버네이트 구성 XML 파일에는 데이터베이스 정보와 매핑 리소스 세부 정보가 포함되어 있습니다. 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="items.hbm.xml"/>
    </session-factory>
</hibernate-configuration>

하이버네이트 일대다 매핑 예제 – XML 구성

튜토리얼의 가장 중요한 부분입니다. 하이버네이트에서 일대다 매핑을 위해 Cart 및 Items 클래스를 어떻게 매핑해야 하는지 살펴봅시다. 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 name="total" />
		</property>
		<property name="name" type="string">
			<column name="name" />
		</property>
		<set name="items" table="ITEMS" fetch="select">
			<key>
				<column name="cart_id" not-null="true"></column>
			</key>
			<one-to-many class="Items"/>
		</set>
	</class>
	
</hibernate-mapping>

중요한 부분은 set 요소와 그 내부의 one-to-many 요소입니다. 일대다 매핑에 사용될 키인 cart_id를 제공하고 있는지 주목하세요. items.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="Items" table="ITEMS">
		<id name="id" type="long">
			<column name="id" />
			<generator class="identity" />
		</id>
		<property name="itemId" type="string">
			<column name="item_id"></column>
		</property>
		<property name="itemTotal" type="double">
			<column name="item_total"></column>
		</property>
		<property name="quantity" type="integer">
			<column name="quantity"></column>
		</property>
		
		<many-to-one name="cart" class="Cart">
			<column name="cart_id" not-null="true"></column>
		</many-to-one>
	</class>

</hibernate-mapping>

items에서 cart로 넘어가면, 다대일 관계입니다. 따라서 cart에 대해 many-to-one 요소를 사용해야 하며, 키와 매핑될 column 이름을 제공합니다. 따라서 Cart 하이버네이트 매핑 구성에 따라 키인 cart_id가 매핑에 사용됩니다. XML 매핑을 사용한 하이버네이트 일대다 매핑 예제 프로젝트가 준비되었습니다. 이제 테스트 프로그램을 작성하고 제대로 작동하는지 확인해 봅시다.

하이버네이트 일대다 매핑 예제 – 테스트 프로그램

HibernateOneToManyMain.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.Items;
import com.journaldev.hibernate.util.HibernateUtil;

public class HibernateOneToManyMain {

	public static void main(String[] args) {

		Cart cart = new Cart();
		cart.setName("MyCart");
		
		Items item1 = new Items("I1", 10, 1, cart);
		Items item2 = new Items("I2", 20, 2, cart);
		Set itemsSet = new HashSet();
		itemsSet.add(item1); itemsSet.add(item2);
		
		cart.setItems(itemsSet);
		cart.setTotal(10*1 + 20*2);
		
		SessionFactory sessionFactory = null;
		Session session = null;
		Transaction tx = null;
		try{
		//세션 가져오기
		sessionFactory = HibernateUtil.getSessionFactory();
		session = sessionFactory.getCurrentSession();
		System.out.println("Session created");
		//트랜잭션 시작
		tx = session.beginTransaction();
		
		//모델 객체 저장
		session.save(cart);
		session.save(item1);
		session.save(item2);
		
		//트랜잭션 커밋
		tx.commit();
		System.out.println("Cart ID="+cart.getId());
		
		}catch(Exception e){
			System.out.println("Exception occured. "+e.getMessage());
			e.printStackTrace();
		}finally{
			if(!sessionFactory.isClosed()){
				System.out.println("Closing SessionFactory");
				sessionFactory.close();
			}
		}
	}

}

Cart 및 Items 객체를 하나씩 저장해야 합니다. Hibernate는 Items 테이블의 외래 키를 업데이트하는 데 관여합니다. 위 프로그램을 실행하면 다음과 같은 출력이 생성됩니다.

Hibernate Configuration loaded
Hibernate serviceRegistry created
Session created
Hibernate: insert into CART (total, name) values (?, ?)
Hibernate: insert into ITEMS (item_id, item_total, quantity, cart_id) values (?, ?, ?, ?)
Hibernate: insert into ITEMS (item_id, item_total, quantity, cart_id) values (?, ?, ?, ?)
Hibernate: update ITEMS set cart_id=? where id=?
Hibernate: update ITEMS set cart_id=? where id=?
Cart ID=6
Closing SessionFactory

Hibernate가 ITEMS 테이블에서 cart_id를 설정하는 데 Update 쿼리를 사용하는 것에 주목하세요.

Hibernate One To Many 매핑 주석

이제 우리는 XML 기반 구성을 사용하여 Hibernate에서 One To Many 매핑을 어떻게 구현하는지 살펴 보았으니, JPA 주석을 사용하는 동일한 작업을 어떻게 수행할 수 있는지 살펴 보겠습니다.

Hibernate One To Many 매핑 예제 주석

Hibernate 구성 파일은 거의 동일하지만 주석을 사용하여 hibernate one to many 매핑에 클래스를 사용하므로 매핑 요소가 변경됩니다. 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.Items1"/>
    </session-factory>
</hibernate-configuration>

Hibernate 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 {
            // 하이버네이트-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;
    }
}

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

XML 기반 매핑 파일이 없기 때문에, 모든 매핑 관련 구성은 모델 클래스의 JPA 어노테이션을 사용하여 수행됩니다. XML 기반 매핑을 이해한다면, 매우 간단하고 유사합니다. Cart1.java

package com.journaldev.hibernate.model;

import java.util.Set;

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

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

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="cart_id")
	private long id;
	
	@Column(name="total")
	private double total;
	
	@Column(name="name")
	private String name;
	
	@OneToMany(mappedBy="cart1")
	private Set items1;
	
// 속성에 대한 Getter Setter 메서드
}

주의할 점은 OneToMany 어노테이션입니다. 여기서 mappedBy 변수는 매핑 목적으로 사용될 Items1 클래스의 속성을 정의합니다. 따라서 Items1 클래스에 “cart1″이라는 속성이 있어야 합니다. 모든 Getter-Setter 메서드를 포함하는 것을 잊지 마십시오. Items1.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.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name="ITEMS")
public class Items1 {

	@Id
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	@Column(name="id")
	private long id;
	
	@Column(name="item_id")
	private String itemId;
	
	@Column(name="item_total")
	private double itemTotal;
	
	@Column(name="quantity")
	private int quantity;
	
	@ManyToOne
	@JoinColumn(name="cart_id", nullable=false)
	private Cart1 cart1;
	
	// 하이버네이트는 매개변수 없는 생성자가 필요합니다
	public Items1(){}
	
	public Items1(String itemId, double total, int qty, Cart1 c){
		this.itemId=itemId;
		this.itemTotal=total;
		this.quantity=qty;
		this.cart1=c;
	}
// Getter Setter 메서드
}

가장 중요한 부분은 Cart1 클래스 변수에 있는 ManyToOne 어노테이션과 매핑을 위해 JoinColumn 어노테이션입니다. 이것으로 하이버네이트에서 어노테이션을 사용하여 모델 클래스에서 일대다 매핑이 완료되었습니다. XML 기반 구성과 비교하면 매우 유사한 것을 알 수 있습니다. 이제 테스트 프로그램을 작성하고 실행합시다.

하이버네이트 일대다 매핑 어노테이션 예제 테스트 프로그램

우리의 테스트 프로그램은 xml 기반 구성과 마찬가지로 Hibernate 세션을 가져오고 모델 객체를 데이터베이스에 저장하는 데 새로운 클래스를 사용하고 있습니다. HibernateOneToManyAnnotationMain.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.Items1;
import com.journaldev.hibernate.util.HibernateAnnotationUtil;

public class HibernateOneToManyAnnotationMain {

	public static void main(String[] args) {

		Cart1 cart = new Cart1();
		cart.setName("MyCart1");
		
		Items1 item1 = new Items1("I10", 10, 1, cart);
		Items1 item2 = new Items1("I20", 20, 2, cart);
		Set itemsSet = new HashSet();
		itemsSet.add(item1); itemsSet.add(item2);
		
		cart.setItems1(itemsSet);
		cart.setTotal(10*1 + 20*2);
		
		SessionFactory sessionFactory = null;
		Session session = null;
		Transaction tx = null;
		try{
		//세션 가져오기
		sessionFactory = HibernateAnnotationUtil.getSessionFactory();
		session = sessionFactory.getCurrentSession();
		System.out.println("Session created");
		//트랜잭션 시작
		tx = session.beginTransaction();
		//모델 객체 저장
		session.save(cart);
		session.save(item1);
		session.save(item2);
		//트랜잭션 커밋
		tx.commit();
		System.out.println("Cart1 ID="+cart.getId());
		System.out.println("item1 ID="+item1.getId()+", Foreign Key Cart ID="+item1.getCart1().getId());
		System.out.println("item2 ID="+item2.getId()+", Foreign Key Cart ID="+item1.getCart1().getId());
		
		}catch(Exception e){
			System.out.println("Exception occured. "+e.getMessage());
			e.printStackTrace();
		}finally{
			if(!sessionFactory.isClosed()){
				System.out.println("Closing SessionFactory");
				sessionFactory.close();
			}
		}
	}

}

위의 하이버네이트 일대다 매핑 어노테이션 예제 테스트 프로그램을 실행하면 다음과 같은 출력이 나옵니다.

Hibernate Annotation Configuration loaded
Hibernate Annotation serviceRegistry created
Session created
Hibernate: insert into CART (name, total) values (?, ?)
Hibernate: insert into ITEMS (cart_id, item_id, item_total, quantity) values (?, ?, ?, ?)
Hibernate: insert into ITEMS (cart_id, item_id, item_total, quantity) values (?, ?, ?, ?)
Cart1 ID=7
item1 ID=9, Foreign Key Cart ID=7
item2 ID=10, Foreign Key Cart ID=7
Closing SessionFactory

이것으로 하이버네이트 일대다 매핑에 관한 설명이 끝났습니다. 아래 링크에서 샘플 프로젝트를 다운로드하여 더 많은 실험을 진행해보세요.

하이버네이트 일대다 매핑 프로젝트 다운로드

Source:
https://www.digitalocean.com/community/tutorials/hibernate-one-to-many-mapping-annotation