Spring/JPA

연관관계 매핑 기초 - 단방향 연관관계

느리지만 꾸준하게 2022. 4. 16. 17:32

단방향 연관관계에 대해서 살펴보자.

 

  • 객체와 테이블 연관관계의 차이를 이해하는거에 집중
  • 객체의 참조와 테이블의 외래 키를 매핑

 

  • 용어 
  • 방향(Direction): 단방향, 양방향
  • 다중성(Multiplicity): 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 이해
  • 연관관계의 주인: 객체 양방향 연관관계는 관리가 필요하다.

 

진행하려는 예제 시나리오를 살펴보면

  • 회원과 팀이 있고
  • 회원은 하나의 팀에만 소속될 수 있고
  • 회원과 팀은 다대일 관계(N:1)

 

아래 매핑관계를 보면 TEAM_ID(FK)값이 MEMBER에 속해있다. 즉 MEMBER가 다대일에서 다이고(N)

TEAM이 다대일에서 일이다.(1)

 

Member Class를 작성

package hellojpa;

import javax.persistence.*;

@Entity
public class Member {

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

    @Column(name = "USERNAME")
    private String username;

    @Column(name = "TEAM_ID")
    private Long teamId;
}

 

Team Class를 작성

package hellojpa;


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

@Entity
public class Team extends BaseEntity{

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

 

JpaMain을 작성해서 실행해보면 Member table과 Team table이 생성되게 된다.

package hellojpa;

import org.hibernate.Hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

public class JpaMain {

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

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}
create table Member (
       MEMBER_ID bigint not null,
        TEAM_ID bigint,
        USERNAME varchar(255),
        primary key (MEMBER_ID)
    )





Hibernate: 
    
    create table Team (
       TEMA_ID bigint not null,
        name varchar(255),
        primary key (TEMA_ID)

 

 

 

 

 

 

 

이제 Team table에 getter setter을 생성해주고

package hellojpa;


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

@Entity
public class Team extends BaseEntity{

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

    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;
    }
}

 

 

member1을 teamA에 소속시켜주고 싶으면 아래와 같이 JpaMain을  작성.

그러면 빌드 실행시 team 쿼리 나가고 member 쿼리가 나가게 된다.

package hellojpa;

import org.hibernate.Hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

public class JpaMain {

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

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {

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

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

            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Team
        */ insert 
        into
            Team
            (name, TEMA_ID) 
        values
            (?, ?)
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (TEAM_ID, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?)

 

 

 

 

h2 DB에서 아래와 같이 쿼리문을 작성하게되면 결과를 확인할 수 있다.

SELECT * FROM MEMBER;

SELECT * FROM TEAM;

SELECT * FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID

 

 

 

 

 

 

 

JpaMain에서 아래코드가 애매한데 외래키 식별자를 직접 다루기 때문이다.

member.setTeamId(team.getId());

 

그리고 찾아온 member가 어느 팀 소속인지 알고 싶으면 JpaMain에서 아래와 같이 코드를 작성하면 된다.

이 코드는 JPA를 통해서 DB를 통해 계속 꺼내기 때문에 객체지향스럽지가 않다.

Member findMember = em.find(Member.class, member.getId());

Long findTeamId = findMember.getTeamId();
Team findTeam = em.find(Team.class, findTeamId);

 

객체를 테이블에 맞추어 데이터 중심으로 모델링을 하게되면, 협력 관계를 만들 수가 없게 된다.

  • 테이블은 외래 키로 조인을 사용해 연관된 테이블을 찾고
  • 객체는 참조를 사용해 연관된 객체를 찾는다.
  • 테이블과 객체 사이에 이러한 큰 간격이 있다.

 

 

객체 지향 스럽게 모델링을 할려면 단방향 연관관계를 통해서 모델링을 하면된다.

 

아래의 객체 지향 모델링(객체 연관관계 사용)을 이용해서 코드를 작성해보자.

 

Member Class에서 Member와 Team이 다대일 관계이므로 ManyToOne 어노테이션을 넣어준다.

그리고 관계를 할 때 조인하는 컬럼이 뭔지 넣기 위해서 JoinColumn을 넣어준다.

package hellojpa;

import javax.persistence.*;

@Entity
public class Member {

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

    @Column(name = "USERNAME")
    private String username;

//    @Column(name = "TEAM_ID")
//    private Long teamId;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    public Long getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Team getTeam() {
        return team;
    }

    public void setTeam(Team team) {
        this.team = team;
    }
}

 

 

 

 

 

이렇게 하게되면 Team과 Team_ID(FK)를 연관관계 매핑을 할 수 있게 된다.(ORM 매핑)

 

 

 

 

 

 

JpaMain을 작성해보자. 실행을 해보면 insert가 제대로 나가고 조회도 제대로 되는 것을 확인 할 수 있다.

즉 객체지향스럽게 레퍼런스들을 가지고 오고 확인을 할 수 있게 된다.

package hellojpa;

import org.hibernate.Hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

public class JpaMain {

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

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {


            // 저장
            Team team =  new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            // 이렇게 해주면 JPA가 알아서 PK값을 team에서 꺼내서 FK값에 insert해준다.
            member.setTeam(team);
            em.persist(member);

            Member findMember = em.find(Member.class, member.getId());

            Team findTeam = findMember.getItem();
            System.out.println("findTeam = " + findTeam.getName());


            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
findTeam = TeamA
Hibernate: 
    /* insert hellojpa.Team
        */ insert 
        into
            Team
            (name, TEAM_ID) 
        values
            (?, ?)
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (TEAM_ID, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?)

 

 

 

 

 

 

em.flush와 em.clear을 넣게되면

 

flush는 영속성 컨텍스트에 있는 것을 db로 다 날리고 clear를 함으로싸 영속성 컨텍스트에 있는것을 초기화 할 수가 있게 된다.

 

빌드를 실행시켜보면 insert 쿼리 두방을 내보내고 select를 내보내게 된다.

// JpaMain Class

package hellojpa;

import org.hibernate.Hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

public class JpaMain {

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

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {


            // 저장
            Team team =  new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            // 이렇게 해주면 JPA가 알아서 PK값을 team에서 꺼내서 FK값에 insert해준다.
            member.setTeam(team);
            em.persist(member);

            em.flush();
            em.clear();

            Member findMember = em.find(Member.class, member.getId());

            Team findTeam = findMember.getItem();
            System.out.println("findTeam = " + findTeam.getName());


            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}

 

 

 

 

 

이렇게 판단할 수 가 있게된다. JPA에서 이렇게 객체지향스럽게 한 것을

객체의 참조와 db의 외래키를 매핑을 해서 연관관계 매핑을 할 수 있구나라고 판단할 수 있게 된다.

Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    call next value for hibernate_sequence
Hibernate: 
    /* insert hellojpa.Team
        */ insert 
        into
            Team
            (name, TEAM_ID) 
        values
            (?, ?)
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (TEAM_ID, USERNAME, MEMBER_ID) 
        values
            (?, ?, ?)
Hibernate: 
    select
        member0_.MEMBER_ID as MEMBER_I1_0_0_,
        member0_.TEAM_ID as TEAM_ID3_0_0_,
        member0_.USERNAME as USERNAME2_0_0_,
        team1_.TEAM_ID as TEAM_ID1_1_1_,
        team1_.name as name2_1_1_ 
    from
        Member member0_ 
    left outer join
        Team team1_ 
            on member0_.TEAM_ID=team1_.TEAM_ID 
    where
        member0_.MEMBER_ID=?
findTeam = TeamA

 

 

 

 

 

 

 

 

그리고 연관관계 수정과 관련된 것은 보면

pk값이 100인 것이 있고 찾은 member에서 newTeam으로 바꾸고 싶은 것이 있으면 아래와 같이 하면 된다.

// 객체지향 모델링 => 연관관계 수정 (db에 100번 team이 있다고 가정을 하고)
            Team newTeam = em.find(Team.class, 100L);

            findMember.setTeam(newTeam);

 

 

package hellojpa;

import org.hibernate.Hibernate;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;

public class JpaMain {

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

        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {


            // 저장
            Team team =  new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member1");
            // 이렇게 해주면 JPA가 알아서 PK값을 team에서 꺼내서 FK값에 insert해준다.
            member.setTeam(team);
            em.persist(member);

            em.flush();
            em.clear();

            Member findMember = em.find(Member.class, member.getId());

            Team findTeam = findMember.getItem();
            System.out.println("findTeam = " + findTeam.getName());


// 객체지향 모델링 => 연관관계 수정 (db에 100번 team이 있다고 가정을 하고)
            Team newTeam = em.find(Team.class, 100L);

            findMember.setTeam(newTeam);


            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }

        emf.close();
    }
}

 

 

 

 

 

 

 

 

 

 

 

 

 

<출처 김영한: 자바 ORM 표준 JPA 프로그래밍 - 기본편 >

https://www.inflearn.com/course/ORM-JPA-Basic/dashboard

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Spring > JPA' 카테고리의 다른 글

예제 - 연관관계 매핑 시작  (0) 2022.05.25
양방향 연관관계와 연관관계의 주인  (0) 2022.05.08
실전 예제 1 - 요구사항 분석과 기본 매핑  (0) 2022.04.14
기본 키 매핑  (0) 2022.04.14
필드와 컬럼 매핑  (0) 2022.04.14