본문 바로가기
Spring/이론

Entity Relationships

by 모스키토끼 2020. 4. 17.

Entity Relationships

relationship multiplicities 4가지 종류

  • @OneToOne
  • @OneToMany
  • @ManyToOne
  • ManyToMany

relationship의 방향

  • bidirectional(양방향): owning side와 inverse side
  • unidirectional(단방향): owning side only

owning side란?

레퍼런스를 가지고 있는 부분(side) -> 다른 쪽을 참조하고 있는 Entity

 

Entity Relation Attributes

- updates/deletes cascadng 지원

  • CascadeType
    • ALL, PERSIST, MERGE, REMOVE, REFRESH

- 관련 행 패치에 performance strategy를 선언 가능

  •  FetchType
    • LAZY, EAGER
    • DB에 Entity를 패치 했을 때, Entity에 속하는 다른 Entity를 같이 읽을 것인가 아닌가를 결정
      EAGER이라고 설정을 하면 같이 읽는다.
      LAZY는 같이 읽지 않는다.
    • Performace관점(메모리관점)에서는 LAZY가 더 좋다. 필요한 것만 읽어들이기 때문, 대신 속도가 늦을 수 있다.

ex)

@ManyToMany(

    cascade = {CascadeType.PERSIST, CascadeType.MERGE},

    fetch = FetchType.EAGER

)

관계 코드에서의 상식!

  • OneToMany관계에서  One side 부분을 parent라고 하고  Many side를 child라고 한다.
  •  Class에 @Entity 어노테이션을 달아주어야 hibernate가 자동으로 이 객체를 table로 mapping 해준다.

1. OneToMany Unidirectional

  • ★OneToMany 관계에서 일반적으로는 Many(child side)에서 reference(forein key)를 가진다.
    ( child가 parent를 참조한다.)
  • 객체에서는 reference는 메모리 주소지만 table에서는 forein key다.
    -> annotation(@ManyToOne(),@JoinColumn())을 달아주어 table에서 forein key로써 처리 될 수 있도록 한다. 

예시 관계 ( Category, Product )

- Category에는 많은 Product가 있을 수 있다.

- 특정 Product는 하나의 Category에만 속한다.

코드

public class Category {

  @Id
  @GeneratedValue
  private int id;

  private String name;
}

public class Product {

  @Id
  @GeneratedValue
  private int id;

  private String name;
  private int price;
  private String description;

  @ManyToOne(cascade=CascadeType.ALL)
  @JoinColumn(name="category_id")
  private Category category;
}
  • @Id로 PK라는 것을 명시
  • @GeneratedValue로 값을 넣어주지 않아도 자동으로 값이 들어감 (1, 2, 3...)
  • @ManyToOne으로 cascade를 ALL로 설정하여 모든 경우에 cascade가 일어나게 한다.
  • @ManyToOne으로 어떤 객체가 자식 객체인지 Hibernate에 알릴 수있다.
  • @OneToMany으로 어떤 객체가 부모 객체인지 Hibernate에 알릴 수있다.
  • @JoinColum으로 조인으로 사용할 열을 지정할 수 있으며 열 이름을 지정할 수도 있다.
sessionFactory = new Configuration().configure()
				.buildSessionFactory();

Category category1 = new Category("가전제품");
Product product1 = new Product("노트북",200, "노트북 좋아",category1);
Product product2 = new Product("TV", 150, "TV 좋아", category1);

Category category2 = new Category("책");
Product product3 = new Product("신세계에서", 2000, "내 최애",	category2);

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.save(product1);
session.save(product2);
session.save(product3);


tx.commit();
session.close();

 

  • session.save를 하는 경우, product에서 Cascade를 All로 설정했기 때문에 Category까지 같이 저장이 된다.
    -> 그렇다면 hibernate는 product를 먼저 만들까 category를 먼저 만들까?
    -> 당연히 Category, 그래야지 foreign키를 채우기 위해서는 참조하려는 것이 있어야지만 가능하기 때문 

2. OneToMany Bidirectional

  • UniDirectional과 테이블상에서는 변화가 없기 때문에 객체에서의 관계만 조금 달라진다.
  • One입장에서는 Many를 가리키는 어러개의 reference가 필요하다.
    ex) One side인 Category에서는 Set을 사용하여 여러개의 product를 참조하고 있다.
  • 자신의 입장에서 oneToMany 인지 ManyToOne지를 쓴다.
    - OneToMany - Category입장
    - ManyToOne - product입장

예시 코드

public class Category {

 @Id
 @GeneratedValue
 private int id;
 private String name;

 @OneToMany(mappedBy="category", cascade=CascadeType.ALL, fetch = FetchType.LAZY)
 private  Set<Product> products; 
}


public class Product {

  @Id
  @GeneratedValue
  private int id;
  …

  @ManyToOne
  @JoinColumn(name="category_id")
  private Category category;
}
  • mappedBy = "여기에 들어가는 것은 Product의 category 필드명 "
    -> Hibernate에게 자식 클래스에있는 부모 클래스를 표현하기 위해 어떤 변수를 사용해야 하는지를 알려주는 것
    -> child에서 parent를 reference하고 있는 부분이 무엇인지를 써주어야지 hibernate가 parent에서 child 방향을 연결해준다.
    ->Table입장에서는 양방향이라고 해도 따로 테이블이 만들어 지는 것이 없다. 따라서 객체 입장에서 설정을 해주어야 한다.
  • unidirectional에서는 child side를 저장을 했지만 Bidirectional에서는 parent side를 저장한다.
    -> 어차피 child가 저장되어야 하기 때문
    -> cacade설정이 parent side로 옮겨갔다. 
  • cascade=CascadeType.ALL
    • Category가 save되면 모든 Product들도 save된다.
    • Category가 delete되면 관련된 Product 인스턴스들도 삭제가 된다.
  • fetch=FetchType.LAZY
    • Category가 로드될 때마다 모든 product가 로드되는 것을 원하지 않음
    • 필요에 의해서 접근할 때만 로드된다.
sessionFactory = new Configuration().configure().buildSessionFactory();

Category category1 = new Category("가전제품");
Product product1 = new Product("노트북",200, "노트북 좋아",category1);
Product product2 = new Product("TV", 150, "TV 좋아", category1);

category1.getProducts().add(product1);
category1.getProducts().add(product2);

Category category2 = new Category("책");
Product product3 = new Product("신세계에서", 2000, "내 최애", category2);

category2.getProducts().add(product3);

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.save(category1);
session.save(category2);

tx.commit();
session.close();
  • 과정
    1. Instantiate/Load parent object
    2. Instantiate/Load child objects
    3. Set the parent object in the child objects
    4. Set the collection of child objects on the parent
    5. Save the parent
  • category에서 set을 사용하였기 때문에 add이라는 메서드를 사용하여 product객체를 넣어준다. 

 

3. OneToOne Unidirectional

예시 관계 ( Person, License )

- Person에는 하나의 License가 존재할 수 있다.

- 하나의 License 한 사람에게 속한다.

코드

public class Person {
  @Id
  @GeneratedValue
  @Column(name="person_id")
  private long id;

  private String firstName;
  private String lastName;

}


public class License {

@Id
@GeneratedValue
@Column(name="license_id")
private long id;

private String license_number;
private Date issue_date;

@OneToOne(optional=false, cascade=CascadeType.ALL)  
@JoinColumn(unique=true, name="person_id")  
private Person person;

}
  • 1대1에선 참조할 값이 유니크 해야되기 때문에 -> @JoinColumn(unique = true, name = ~~);
sessionFactory = 
	new Configuration().configure().buildSessionFactory();

Person person = new Person();
person.setFirstName("대환");
person.setLastName("권");

License license = new License();
license.setLicense_number("DT123548");
license.setIssue_date(new Date());

license.setPerson(person);

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.save(license);

tx.commit();
session.close();

 

  • @JoinColumn이 License에 있으므로 session.save(license)로 License를 save해준다.

4. One-To-One bidirectional

예시 코드

public class Person {
@Id
@GeneratedValue
@Column(name="person_id")
private long id;

private String firstName;
private String lastName;

@OneToOne(mappedBy="person", cascade=CascadeType.ALL)
private License license;

}
  • @OneToOne이 Clind(License)에서 Parent(Person)으로 넘어갔다.
  • mappedBy 속성을 사용하여 License안에서 Person entity 참조에 사용되는 변수명(person)을 명시
sessionFactory = 
	new Configuration().configure().buildSessionFactory();

Person person = new Person();
person.setFirstName("대환");
person.setLastName("권");

License license = new License();
license.setLicense_number("DT516841");
license.setIssue_date(new Date());

license.setPerson(person);

person.setLicense(license);

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.save(person);

tx.commit();
session.close();
  • 양방향이기 때문에 license도 person을 넣고 person도 license를 넣는다.
  • person을 저장하면 cascade 설정으로 license도 저장이 된다.

5. ManyToMany Unidirectional

  • 조인테이블을 하나 만들어서 다대다 관계를 관리한다.
  • 조인테이블은 두 테이블의 primary key만을 참조한다.

코드

public class Author {

  @Id
  @GeneratedValue
  @Column(name="author_id")
  private long id;

  @Column(name="author_name")
  private String name;
}


public class Book {
   @Id
   @GeneratedValue
   @Column(name = "book_id")
   private long id;

   @Column(name = "book_name")
   private String title;

   @ManyToMany(cascade = CascadeType.ALL)
   @JoinTable(name = "author_book", 
      joinColumns = { @JoinColumn(name = "book_id") }, 
      inverseJoinColumns = { @JoinColumn(name = "author_id") })
   private Set<Author> authors ;

}
  • Set을 사용하여 Author를 참조하였다.
  • @JoinTable을 사용하여 조인테이블의 이름과 joinColumns에 @JoinColumn으로 한쪽의 primary key를 설정하고  inverseJoinColums 에다가 또 JoinColumn을 사용하여 다른 쪽 primary key를 설정한다. 
Book book1 = new Book();
book1.setTitle("과학책");

Book book2 = new Book();
book2.setTitle("수학책");

Book book3 = new Book();
book3.setTitle("영어책");


Author author1 = new Author();
author1.setName("권대환");

Author author2 = new Author();
author2.setName("건대한");

Author author3 = new Author();
author3.setName("곤대훈");

Set<Author> scienceAuthors = new HashSet<Author>();
Set<Author> mathAuthors = new HashSet<Author>();
Set<Author> englishAuthors = new HashSet<Author>();

scienceAuthors.add(author1);
scienceAuthors.add(author2);
scienceAuthors.add(author3);

mathAuthors.add(author1);
mathAuthors.add(author2);

englishAuthors.add(author2);
englishAuthors.add(author3);

book1.setAuthors(scienceAuthors);
book2.setAuthors(mathAuthors);
book3.setAuthors(englishAuthors);

sessionFactory = new Configuration().configure().
				buildSessionFactory();

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

session.save(book1);
session.save(book2);
session.save(book3);

tx.commit();
session.close();
  • HashSet으로 여러 Author들을 묶고 book에 넣어주면
    book은 여러 명의 author가 있는 것이고
    author가 여러 방법으로 묶였으므로(author가 여러번 사용됨) 한 author가 여러 book의 author로 들어갔다.
    따라서 하나의 책하나에 여러명의 저자, 한명의 저자에 여러 개의 책 즉 다대다 관계이다.
  • @JoinColumn이 Book에 있으므로 save를 book 객체로 한다.

Reference

https://howtoprogramwithjava.com/hibernate-manytomany-unidirectional-bidirectional/

 

Hibernate @ManyToMany Unidirectional and Bidirectional - How to Program with Java

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

howtoprogramwithjava.com

'Spring > 이론' 카테고리의 다른 글

Restful Web Service  (0) 2020.04.19
Hibernate with Spring  (0) 2020.04.19
Hibernate  (0) 2020.04.10
Apache Tiles  (0) 2020.04.08
Logging (SLF4J and Logback)  (0) 2020.04.01

댓글