JPA

연관관계 매핑 기초 (section 5)

challnum 2022. 8. 16. 21:55

1 : N의 관계

@Entity
public class Team {

    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
    //    1 대 N이며 team에 의해서 관리되고 있다는 것을 의미하기에 연관관계 주인은 team이라는 것을 의미하며
    //    members에 값을 넣어도 아무 관계가 벌어지지 않으며 team에서 모든 수정 작업이 이뤄진다. 값을 변경시에 team만을 참조한다.
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();

-----------------------------------------------------------------------------------------

@Entity
public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
//    @Column(name = "TEAM_ID")
//    private Long teamId;
    @Column(name = "USERNAME")
    private String username;

//    하나의 Team에 여러명의 Member가 포함이 되므로 1 : N의 관계
    @ManyToOne
//    연결해야 하는 columns의 값의 명 기입
//    JoinColumn으로 매핑을 함 관리를 한다를 의미
    @JoinColumn(name = "TEAM_ID")
    private Team team;

-----------------------------------------------------------------------------------------
객체 지향적이지 않은 코드
 
 public class Member {
    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;
  
 ----------------------------------------------------------------------------------------
 
 public class Team {

    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
  
 ----------------------------------------------------------------------------------------
 
 Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            member.setTeamId(1L);
            em.persist(member);
            tx.commit(); 
 
 ----------------------------------------------------------------------------------------
 
 이 방법의 경우 외래키를 참조 하는 것이 아닌 직접 값을 들고오는 형식을 가지게 된다.

위와 같은 그림의 객체 지향적 관점에서의 테이블 맵핑은 아래와 같은 코드로 구성이 된다.

@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
//    @Column(name = "TEAM_ID")
//    private Long teamId;
@Column(name = "USERNAME")
private String username;

//    하나의 Team에 여러명의 Member가 포함이 되므로 1 : N의 관계
@ManyToOne
//    연결해야 하는 columns의 값의 명 기입
@JoinColumn(name = "TEAM_ID")
private Team team;

----------------------------------------------------------------------

@Id
@GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;

----------------------------------------------------------------------
    
Team team = new Team();
team.setName("TeamA");
em.persist(team);

Member member = new Member();
member.setUsername("member1");
member.setTeam(team);
em.persist(member);
tx.commit();

----------------------------------------------------------------------

이 같은 경우는 단방향으로 맴버에서 팀으로 이동이 가능하지만 팀에서 맴베로 이동이 불가능하다.

양방향 매핑
@Entity
public class Team {

    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;
//	mappedBy = 
    @OneToMany(mappedBy = "team")
//	ArrayList초기화를 방지하기 위해서 선언
    private List<Member> members = new ArrayList<Member>();
---------------------------------------------------------------------------------------------
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
//    @Column(name = "TEAM_ID")
//    private Long teamId;
    @Column(name = "USERNAME")
    private String username;

//    하나의 Team에 여러명의 Member가 포함이 되므로 1 : N의 관계
    @ManyToOne
//    연결해야 하는 columns의 값의 명 기입
    @JoinColumn(name = "TEAM_ID")
    private Team team;
---------------------------------------------------------------------------------------------

           Member findMember = em.find(Member.class, member.getId());
            List<Member> members = findMember.getTeam().getMembers();
            for (Member m : members){
                System.out.println(m.getUsername());
            }


---------------------------------------------------------------------------------------------

객체를 위와 같이 매핑했을 경우 하나의 고민이 생기게 된다.

  • MEMBER의 team값을 바꿨을 때 외래키 값이 업데이트 되어야 하는지 Team의 members을 업데이트 했을때인지 생각하게 된다.
  • 그렇기에 둘 중 하나로 양방향 매핑에서 연관관계의 주인을 정하게 된다.

mappedBy : 어떤 것에 의해서 매핑이 되었다를 의미


  • OneToMany에 해당하는 1에 해당하는 Class에 mappedBy를 걸게 한다.
  • List members의 값은 읽기만 가능한 값이다.
  • 외래키가 있는 곳을 주인으로 정해야 하며 DB의 관점에서 보게 된다면 외래키가 있는 곳이 N에 해당하게 된다.
  • 자동차에 비유했을 때 차와 바퀴가 있을 경우 바퀴가 연관관계의 주인이 되어야 한다.(설계부분이나 성능 이슈를 해결하게 되며 왜래키와 Entity를 한꺼번에 관리를 할 수 있다.)

Member member = new Member();
            member.setUsername("member1");
            em.persist(member);

            Team team = new Team();
            team.setName("TeamA");
            team.getMembers().add(member);
            em.persist(team);

TEAM의 ListMember같은 경우 읽기 전용이기에 DB를 본다면 아래와 같이 team.getMembers().add(member); 같은 경우 TEAM_ID의 값이 null이 들어간 것을 볼 수 있다,

Team team = new Team();
            team.setName("TeamA");
            em.persist(team);
            
            Member member = new Member();
            member.setUsername("member1");
            member.setTeam(team);
            em.persist(member);

하지만 연관관계의 주인인 외래키가 포함된 member에서의 setTeam을 사용한 경우 정상적으로 Tema_ID의 값이 들어가는 것을 볼 수 있다.
Lombok의 toString()같은 경우 사용하지 않는 것이 좋으며 controller에는 entity를 절대 반환하면 안된다. entity를 반환하는 경우 api 스펙이 바뀔수도 있다. Entity는 Dto(값만 반환하는)로 반환하는 것이 옳다.

양 방향 매핑의 값을 양쪽 다 넣어줘야 하며 이 같은 방법은 2가지 정도가 있다. 

team에 member의 값을 넣는 방법
1. 
Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            em.persist(member);

            team.addMember(member);

----------------------------------------------------------------------------------

public void addMember(Member member){
        member.setTeam(this);
        members.add(member);
    }



----------------------------------------------------------------------------------

member에 team의 값을 넣는 방법 
2.
Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            member.changeTeam(team);
            em.persist(member);

----------------------------------------------------------------------------------

    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }

하지만 한 부분에 값을 넣어야 하며 두 공간 모두에 값을 넣어주는 방법을 택한다면 무한 루프에 빠지는 경우가 생길 수도 있다.

  1. 처음 설계시에는 단방향 매핑으로 설계를 시작한다.
  2. 양방향 매핑같은 경우 역방향의 참조를 하게 되는 경우에 필요하다.
  3. 단방향을 설계해둔 경우 양방향 매핑 같은 경우 필요시 추가하면 된다.
  4. 예를 들어 지금까지의 코드 내용을 보게 된다면 Team 안에 ListMember만 추가 하게 된다면 양방향으로 바뀌었던 것처럼 단방향 매핑이 끝난 경우 양방향 전환을 할 때 양방향 매핑시 테이블을 건드리지 않는다.

package jpabook.jpashop.domain;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "ORDERS")
public class Order {
    @Id
    @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;

    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
//  order_id와 매핑된 것이 orderitem의 order이기에
    @OneToMany(mappedBy = "order")
    private List<OrderItem> orderItems = new ArrayList<>();

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;


    public void addOrderItem(OrderItem orderItem){
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    public Long getId() {
        return id;
    }

    public Member getMember() {
        return member;
    }

    public void setMember(Member member) {
        this.member = member;
    }

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

    public LocalDateTime getOrderDate() {
        return orderDate;
    }

    public void setOrderDate(LocalDateTime orderDate) {
        this.orderDate = orderDate;
    }

    public OrderStatus getStatus() {
        return status;
    }

    public void setStatus(OrderStatus status) {
        this.status = status;
    }
}


------------------------------------------------------------------------------
package jpabook.jpashop.domain;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
public class Member {
    @Id
    @GeneratedValue()
    @Column(name = "MEMBER_ID")
    private Long id;
    private String name;
    private String city;
    private String street;
    private String zipcode;
//  비즈니스상 아래의 코드는 없는 것이 더 좋은 관계이다.
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();

    public String getName() {
        return name;
    }

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

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public Long getId() {
        return id;
    }

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

    public String getStreet() {
        return street;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public String getZipcode() {
        return zipcode;
    }

    public void setZipcode(String zipcode) {
        this.zipcode = zipcode;
    }
}







------------------------------------------------------------------------------


package jpabook.jpashop.domain;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

//        DB의 커넥션을 하나 얻은 것이다.
        EntityManager em = emf.createEntityManager();
//        Transaction 시작
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        try {
            Order order = new Order();
            em.persist(order);

            OrderItem orderItem = new OrderItem();
            orderItem.setOrder(order);
            em.persist(orderItem);

            tx.commit();
        } catch (Exception e){
            tx.rollback();
        } finally {
//            EntityManager가 DB커넥션을 물고 동작하기에 사용을 하고 닫아줘야 한다.
            em.close();
        }
        emf.close();
    }
}


------------------------------------------------------------------------------




package jpabook.jpashop.domain;

import javax.persistence.*;

@Entity
public class OrderItem {
    @Id
    @GeneratedValue
    @Column(name = "ORDER_ITEM_ID")
    private Long id;
    @ManyToOne
    @JoinColumn(name = "ORDER_ID")
    private Order order;
    @ManyToOne
    @JoinColumn(name = "ITEM_ID")
    private Item item;

    private int orderPrice;
    private int count;

    public Long getId() {
        return id;
    }

    public Order getOrder() {
        return order;
    }

    public void setOrder(Order order) {
        this.order = order;
    }

    public Item getItem() {
        return item;
    }

    public void setItem(Item item) {
        this.item = item;
    }

    public void setId(Long id) {
        this.id = id;
    }
    public int getOrderPrice() {
        return orderPrice;
    }
    public void setOrderPrice(int orderPrice) {
        this.orderPrice = orderPrice;
    }
    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
}

------------------------------------------------------------------------------


package jpabook.jpashop.domain;

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

@Entity
public class Item {

    @Id
    @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;

    private String name;
    private int price;
    private int stockQuantity;

    public Long getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getPrice() {
        return price;
    }

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

    public int getStockQuantity() {
        return stockQuantity;
    }

    public void setStockQuantity(int stockQuantity) {
        this.stockQuantity = stockQuantity;
    }
}

 

 

@ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
--------------------------------------------------------------------------------

단반향의 설계시 
Hibernate: 
    
    create table Member (
       MEMBER_ID bigint not null,
        USERNAME varchar(255),
        TEAM_ID bigint,
        primary key (MEMBER_ID)
    )
Hibernate: 
    
    create table Team (
       TEAM_ID bigint not null,
        name varchar(255),
        primary key (TEAM_ID)
    )
Hibernate: 
    
    alter table Member 
       add constraint FKl7wsny760hjy6x19kqnduasbm 
       foreign key (TEAM_ID) 
       references Team
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (TEAM_ID, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?)
Hibernate: 
    /* insert hellojpa.Team
        */ insert 
        into
            Team
            (name, TEAM_ID) 
        values
            (?, ?)
8월 23, 2022 3:37:40 오후 org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl stop
INFO: HHH10001008: Cleaning up connection pool [jdbc:h2:tcp://localhost/~/test]

종료 코드 0(으)로 완료된 프로세스

'JPA' 카테고리의 다른 글

다양한 연관관계 매핑 (section 6)  (0) 2022.08.23
엔티티 매핑(section 4)  (0) 2022.08.14
영속성 컨텍스트(section 3)  (1) 2022.08.09
Transactional(section 2)  (0) 2022.08.05
JPA의 소개(section1)  (0) 2022.08.05