자라선

[자바 ORM 표쥰 JPA 프로그래밍] 11일차 - 엔티티 및 테이블 연관관계 본문

Develop/JPA

[자바 ORM 표쥰 JPA 프로그래밍] 11일차 - 엔티티 및 테이블 연관관계

자라선 2021. 8. 24. 17:29

1. 단방향 매핑

테이블은 JOIN 를 사용하여 어떤 테이블이 주 테이블이던 간에 서로간의 테이블의 컬럼 데이터를 조회할 수있다.

하지만 객체는 객체 끼리의 참조가 되어버리기 때문에 테이블과 같이 양방향에서 조회할 수는 없다.

 

@Data
@Entity
public class Member {

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

    @Column(name = "USER_NAME")
    private String name;

    @ManyToOne	// N:1 관계를 나타내며 1개의 TEAM 에는 여러 MEMBER가 있을수 있다.
    @JoinColumn(name = "TEAM_ID")	// Team 엔티티의 PK 
    private Team team;

}
@Data
@Entity
public class Team {

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

    @Column(name = "TEAM_NAME")
    private String name;

}

예시로는 Member.getTeam().getName() 과 같이 Member 에서는 Team 엔티티를 참조하여 가져올 수는 있으나

Team 객체에서는 Member를 호출하지 못하게 된다.

설령 Team 객체에 Member 라는 필드를 생성하더라도 이것은 서로서로 단방향 매핑을 한 것이지 

테이블 처럼 양방향 매핑이라고 할 수는 없다.

 

    public static void login(EntityManager em){

        Team team = new Team();
        team.setName("새로운 팀");
        em.persist(team);

        Member member = new Member();
        member.setName("팀원1");
        member.setTeam(team);
        em.persist(member);

        Member member1 = new Member();
        member1.setName("팀원2");
        member1.setTeam(team);
        em.persist(member1);

        // 단방향 매핑을 하여 Member -> Team 를 매핑
        System.out.println("팀 이름: " + member.getTeam().getName());
        
    }

 

2. 양방향 매핑 (객체 그래프 탐색 기능 추가)

객체끼리의 양방향 매핑이 불가능하기 때문에 JPA는 이를 지원해주고자 주인이라는 개념을 만들었다.

엔티티 끼리의 외래키를 관리하는 객체를 주인이라하고 주인 엔티티를 기준으로 서로간의 매핑을 하게 되는것이다.

 

주인은 무조건 외래키를 관리하는 엔티티로 지정하게 된다.

DB 관점으로 보자면 MEMBER N:1 TEAM 의 관계가 되고 아래와 같이 ERD가 만들어진다.

 

1:N

서로의 매핑를 관리해주는 FK 즉 외래키는 MEMBER가 TEAM_ID 라는 컬럼으로 관리하고 있기 때문에 

이 테이블의 주인은 MEMBER가 된다.

 

@Data
@Entity
public class Member {

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

    @Column(name = "USER_NAME")
    private String name;

    @ManyToOne  // 주인 엔티티는 N:1 로 N이 되므로 @ManyToOne 를 사용
    @JoinColumn(name = "TEAM_ID")   // 매핑할 컬럼을 지정
    private Team team;

}

주인 엔티티에서는 @ManyToOne 어노테이션을 사용하여 N 관계인걸 명시해주고

@JoinColumn 어노테이션으로 매핑할 컬럼을 지정해준다.

name은 필수값이 아니긴하다 default 값은 쓰기엔 적합하지않아서 일반적으로는 명시해는편

 

@Data
@Entity
public class Team {

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

    @Column(name = "TEAM_NAME")
    private String name;

    @OneToMany(mappedBy = "team")   // 양방향 매핑을 위해 필드를 추가, mappedBy 로 매핑할 필드명을 지정
    private List<Member> members = new ArrayList<Member>();

}

주인과 매핑할 대싱인 Team 엔티티에서는 @OneToMany 어노테이션으로 1 관계라는것을 명시한다.

이때 필수로 mappedBy 속성을 지정해주어야하는데.

 

주인이 아닌 이 엔티티와 관계를 지을 필드를 작성해야한다.

그리고 List 컬렉션을 사용해 인스턴스 객체까지 생성하고 제네릭으로 주인 엔티티를 지정한다.

 

    public static void view(EntityManager em){
        Team findByTeam = em.find(Team.class, (long) 1);
        
        System.out.println(findByTeam.getName());
        
        // Team 엔티티의 1 ID에 속한 모든 Member 객체를 호출
        for (Member m : findByTeam.getMembers()) {
            System.out.println("MEMBER = " + m.getName());
        }
    }

그리고 조회하게 된다면 Team만 SELECT 했을뿐인데 관련된 모든 Member 객체를 호출하여 가져왔다.

 

3. 더 쉽게 정리

아직도 이해가 잘안되는데... 머릿속에서 최대한 간단하게 생각해보니깐 

 

객체 관점으로 살펴봤을때 Member 객체를 사용하여 Team 객체를 가져오고 싶으면

member.getTeam() 으로 호출하면 된다.  즉 단방향 관점으로만 본다면 간편하지만....

 

Team 객체를 사용하여 포함된 모든 Member 를 가져오려면 결국 1:N 관계에서의

N의 여러 ROW가 발생되기 때문에 무조건 List 와 같은 컬렉션으로 받아야한다.

그러면 Team 객체는 아래와 같은 필드가 필요하게 될 것이고.

private List<Member> members = new ArrayList<Member>();

이렇게 N개의 ROW를 반환한다면 @OneToMany 가 된다. (Many 가 To 뒤에 있으니 N개의 Row와 관계라는 뜻)

그리고 테이블특성상 N 관계인 테이블 Member 가 외래키를 갖고있어

JPA도 @OneToMany 에서만 mappedBy를 지원한다.

 

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();

그러면 주인이 아닌 엔티티에서는 위와 같은 필드가 나오게 되고 양방향 매핑이 끝이 난다.

 

4. 주의사항

보기만하면 뭐 별거없네 하는데 이게 막상사용하면 헷갈린다...

 

 - 1

    public static void edit(EntityManager em){
        Team findByTeam = em.find(Team.class, (long) 1);
        findByTeam.getMembers().clear();    // Team 1 ID의 모든 MEMBER의 매핑을 제거??
    }

find 를 사용하여 DB상의 ID가 1번인 Team 테이블을 SELECT 하였다.

그리고 TEAM 테이블과 매핑되어있는 MEMBER 객체를 전부 제거하였다...

 

이것은 절대 DB 상에 반영되지않는다.

왜냐하면 주인 엔티티가 아니기 때문이다.

주인이 아닌 엔티티에서 @OneToMany로 지정된 필드는 Only 조회용이다.

값을 수정, 삭제, 추가하더라도 그것은 JAVA 상에서만 적용되지 절대 영속성 컨텍스트는 물론 DB에도 반영이 안된다.

 

 

 - 2

    public static void login(EntityManager em){

        Team team = new Team();
        team.setName("새로운 팀");
        em.persist(team);

        Member member = new Member();
        member.setName("팀원1");
        member.setTeam(team);
        em.persist(member);

        Member member1 = new Member();
        member1.setName("팀원2");
        member1.setTeam(team);
        em.persist(member1);

        // ID가 1인 Team 객체를 조회하여 호출 
        Team findByTeam = em.find(Team.class, (long) 1);

        // Team 엔티티의 1 ID에 속한 모든 Member 객체를 호출
        for (Member m : findByTeam.getMembers()) {
            System.out.println("MEMBER = " + m.getName());
        }
    }

 

하나의 트랜잭션 상에서 persist 를 사용해 엔티티들을 영속성 컨텍스트로 등록하였다.

물론 Member와 Team 엔티티를 매핑 하고 난뒤에다.

 

여기까지는 문제가 없으나 바로 find 를 사용해 영속성컨텍스트 상의 ID 1번의 Team 객체를 호출 후

매핑된 Member의 객체를 가져오려했다.

 

당연히 List의 size는 0이 되고 매핑된 정보는 없었다.

 

이유는 간단하다.. 매핑 할때 Member -> Team 으로만 매핑하였고 Team 엔티티는 Member와 매핑한 적이 없기 떄문.

DB상에 올라가 있다면 JPA가 서로 JOIN 후 불러오기 때문에 Team 객체에 Member 리스트가 있겠지만

동일한 트랜잭션이기 때문에 아직 DB로 INSERT도 안된 것이다...

Comments