쿄코코 2025. 2. 1. 16:24
반응형
1. 객체와 테이블 연관관계의 차이를 이해
2. 객체의 참조와 테이블의 외래 키를 매핑
3. 용어 이해
- 방향(Direction): 단방향, 양방향
- 다중성(Multiplicity): 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M) 이해
- ⭐️연관관계의 주인(Owner)⭐️ : 객체 양방향 연관관계는 관리 주인 이 필요

 

예제 시나리오

  • 회원과 팀이 있다.
  • 회원은 하나의 팀에만 소속될 수 있다.
  • 회원과 팀은 다대일 관계다

1. 객체를 테이블에 맞추어 모델링(연관관계가 없는 객체)

 

1. Member 객체

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

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

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

 

2. Team 객체

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

    private String name;

 

3. jpaMain

            Team team = new Team();
            team.setName("TeamA");
            em.persist(team); //PK값이 세팅 되기때문에 -> get을 이용해서 사용가능

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

 

=> 이렇게 하면  member.setTeamId(team.getId()); 이부분에서 외래키 식별자를 직접 다루는 방식이므로 권장되지 않는 방식

 

            Member findMember = em.find(Member.class,member.getId());
            
            //찾아온 멤버가 어느 팀 소속인지 알고 싶은 경우
            Long findTeamId = findMember.getTeamId();
            Team findTeam = em.find(Team.class, findTeamId);

 

객체를 테이블에 맞추어 데이터 중심으로 모델링하면 협력 관계를 만들 수 없다.
왜냐하면 테이블은 JOIN을 사용해서 연관된 테이블을 찾지만, 
객체는 참조를 사용해서 연관된 객체를 찾기 때문이다.


 

2. 단방향 연관관계

위에서 설명한 것을 객체 지향 모델링으로 바꾼다고 한다면

(하나의 팀에는 여러명의 멤버가 소속되어있는 관계 => TEAM : MEMBER 관계는 1: N 관계

@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,Member 관계는 1:N 관계
    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private Team team;
}

 

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

        EntityManager em = emf.createEntityManager();
        //JPA에서는 트랜잭션 단위가 중요. 꼭 트랜잭션 안에서 작업해야함.
        EntityTransaction tx = em.getTransaction();//트랜잭션 얻기
        tx.begin();

        try{
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team); //PK값이 세팅 되기때문에

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

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

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

            tx.commit();

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

        emf.close();
    }
}

 


⭐️⭐️양방향 연관관계 와 연관관계의 주인⭐️⭐️

단방향 연관관계

  • 한쪽 방향에서만 연관관계 설정
  • ex) 단방향 연관관계에서는 Member Team을 참조하지만, Team Member를 모릅니다.
    즉, Team에서는 Member 를 조회 할 수 없음.

양방향 연관관계

  • 두 방향에서 서로 연관 관계를 참조
  • ex) Member는 자신이 속한 Team을 참조하고,
    Team도 자신의 멤버들(Member 리스트)을 참조
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

import java.util.List;

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

        EntityManager em = emf.createEntityManager();
        //JPA에서는 트랜잭션 단위가 중요. 꼭 트랜잭션 안에서 작업해야함.
        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.setTeam(team);
            em.persist(member);

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

            Member findMember = em.find(Member.class, member.getId());
            List<Member> members = findMember.getTeam().getMembers();

            for(Member m: members)
            {
                System.out.println("m= "+m.getUsername()); //m=member1
            }

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

        emf.close();
    }
}

 

객체의 양방향 연관관계와 테이블의 양방향연관관계의 차이

틍직 객체 테이블
양방향 관계 표현 두 개의 단방향 참조 필요( A-> B, B-> A ) 외래키 하나로 양방향 탐색 가능
관계 관리 책임 코드에서 관계를 직접 관리(setTeam, addMember) 데이터베이스가 외래 키를 통해 관계 관리
유지보수 비용 양방향 관계를 일관되게 관리 외래 키로 자동 관리

=> ORM (ex. JPA)를 활용하면 이 간극을 줄일수 있음

 

연관관계의 주인(Owner)

→ 양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정) - @ManyToOne 쪽에서 관리
  • 주인이 아닌쪽은 읽기만 가능( => mappedBy 가 있는 쪽은 읽기만 가능) 
  • 주인은 mappedBy 속성을 사용하지 않는다.
  • 주인이 아니면 mappedBy속성으로 주인을 지정한다. 
@Entity
public class Team {
    @OneToMany(mappedBy = "team") // Team은 주인이 아님 => 읽기 전용으로 설정
    private List<Member> members = new ArrayList<>();
}

@Entity
public class Member {
    @ManyToOne
    @JoinColumn(name = "team_id") // Member가 주인
    private Team team;
}

 

  • 외래키가 있는 곳을 주인으로 !! ( ex. 차와 바퀴가 있다고 하면 - 바퀴가 주인인 상황 ) 
  • Member.team이 연관관계의 주인으로 

 

양방향 매핑 시 가장 많이 하는 실수

(연관관계의 주인에 값을 입력하지 않음)

            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 team = new Team();
            team.setName("TeamA");
            em.persist(team);

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

 

⚠️ 결론 양방향 연관관계시 주의할점 ⚠️

1. 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정
2. 연관관계 편의 메소드를 생성 
3. 양방향 매핑시에 무한 루프를 조심
   - ex.  toString(), lombok, JSON 생성 라이브러리
    (추가. Controller에는 Entity 반환하지 말기- Entity 변경 될수 있기에. DTO로 반환하기)
/////TEAM 객체에 추가 - 연관관계 편의 메소드 //////
/////개인적인 취향으로 해당 로직을 이름을 변경 -> setTeam -> changeTeam으로 변경/////
    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);//나자신
    }
            
            
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);

            Member member = new Member();
            member.setUsername("member2");
            /////// !!!밑 코드 지우기////////
            //team.getMembers().add(member);
            //연관관계의 주인에 값 설정
            member.changeTeam(team);

 


양방향 매핑 정리

1. 단방향 매핑만으로도 이미 연관관계 매핑은 완료
2. 양방향 매핑은 반대 방향으로 조회( 객체 그래프 탐색)
3. JPQL에서는 역방향으로 탐색할일이 많음
4. 연관관계의 주인은 외래키의 위치를 기준으로 정하면 된다!
----단반향 매핑을 잘하고 양방향은 필요할때 추가해도 됨( 테이블에 영향을 주지 않음) --- 

 

반응형