[ 국비학원수료! 근데 JPA를 안 배웠네? ] 연관관계 매핑 기초

JYS·2024년 1월 5일
0
post-thumbnail

🤔 기존의 문제점

객체를 테이블에 맞추어 모델링

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);
  //조회
  Member findMember = em.find(Member.class, member.getId());
  
  Long findTeamId = findMember.getTeamId(); // 맴버객체에 저장된 팀아이디 즉 외래키를 찾는다
  Team findTeam = em.find(Team.class, findTeamId); //찾은 외래키를 사용해서 다시 조회

  tx.commit();
}

위의 코드처럼 객체를 테이블에 맞추어서 모델링했기 때문에 객체를 다루는것이 아니라 관계형데이터베이스와 같은 방식으로 객체를 다루고 있을 뿐 아니라 절차가 복잡하게 느껴진다.

  • 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾는다
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.



✔ 객체 지향 모델링

🔎 단방향 매핑

public class Member {
    @Id
    @GeneratedValue
    private Long id;

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

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

Member클래스 입장에서 Team과 N : 1 관계이기 때문에 @ManyToOne 어노테이션을 사용하여 객체의 참조관계와 테이블의 외래 키를 매핑한다.

try{
  //팀 등록
  Team team = new Team();
  team.setName("TeamA");
  em.persist(team);
  //맴버 등록
  Member member = new Member();
  member.setUsername("member1");
  member.setTeam(team); // 팀의 아이디가 아닌 팀 객체를 그대로 세팅
  em.persist(member);
  //조회
  Member findMember = em.find(Member.class, member.getId());
  
  findMember.getTeam(); // 바로 findMember와 연관된 팀객체를 조회할 수 있다.

  tx.commit();
}
...

객체지향 스럽게 바로 연관된 레퍼런스들을 가지고올 수 있다.

🔎 양방향 매핑

관계형 데이터베이스에서는 외래 키 하나로 양쪽 테이블을 조인해서 Member와 Team의 정보를 조회할 수 있었지만 객체를 사용해서는 불가능했다.
양방향 조회가 가능하도록 관계가 되는 두 객체(Entity)사이에 모두 연관관계를 설정해 줌으로써 양방향 조회,수정이 가능해진다.


💡 연관관계 주인? mappedBy?
양방향 관계에서 예를들어 수정을 해야한다면 Member클래스의 team을 수정 해야할까요? Team의 members 리스트를 수정해야 할까요? 라는 의문이 생길 수 있다.
외래 키를 어디서 관리할지의 문제가 생기게 되는데 이러한 문제를 해결하기위해 외래키를 관리하지 않는 객체에 mappedBy을 사용해서 다른 객체 즉 연관관계의 주인으로부터 매핑 되어젔다 라고 설정하여 주인 객체만이 등록, 수정이 가능하고 매핑되어진 객체는 읽기만 가능하다.

  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성을 사용하지 않고, 아닌쪽은 mappedBy 속성으로 주인을 지정해준다.

💡 누구를 연관관계의 주인으로?
외래 키가 있는 곳을 주인으로 지정한다.

DB관점에서 보면 FK가 있는곳이 N이고 PK쪽이 1이다 (N : 1)
FK를 관리하는 테이블(Member)과 대응되는 객체(Member)를 연관관계의 주인으로 설정해야 update했을때 직관적으로 알기 쉽다.

💡 연관관계의 주인쪽에만 값을 세팅해주면 될까?

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 mbr : members){
                System.out.println("mbr = " + mbr.getUsername());
            }

            tx.commit();
      }

위 코드에서는 정상적으로 member리스트를 조회할 수 있다 그 이유는 flush를 통해 sql문을 commit이전에 강제로 요청한 후 clear를 사용해서 1차캐시를 비워줌으로써 다음의 코드인 em.find를 통해 조회될 때 1차캐시의 데이터가 없기때문에 DB에 select문을 요청하게되고, 연관관계 매핑에 의해서 연관된 테이블을 join하여 member리스트도 select하여 1차캐시에 저장하게 된다. 이때문에 member리스트가 조회될 수 있었다.
하지만 flush와 clear를 하지 않는다면 연관관계가 설정되기 이전의 팀과 맴버 객체가 1차 캐시에 그대로 남아있기 때문에 member리스트는 아무런 값도 가지고 있지 않게된다.
이러한 문제를 해결하기위해 양방향 연관관계를 사용할 때에는 양쪽에 다 값을 세팅해주는것이 맞다.

try {
            //팀 등록
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);
            //맴버 등록
            Member member = new Member();
            member.setUsername("member1");
            member.setTeam(team); // 연관관계의 주인쪽에 값 세팅 1
            em.persist(member);
            
            team.getMembers().add(member); // 연관관계의 주인이 아닌쪽에 값 세팅 2

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

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

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

            tx.commit();
      }

순수 객체 상태를 고려해서 항상 양쪽에 값을 설정 해야한다.
값을 2번 설정하기때문에 실수에 여지가 있다 이러한 실수를 줄이고자 편의 메소드를 생성할 수 있다.

// Memeber
public void changeTeam(Team team){
	this.team = team; //team을 설정해주는 시점에
    team.getMembers.add(this); //바로 this(Member)를 member리스트에 추가
}

📌

  • toString(), lombok, JSON생성 라이브러리 등 무한루프를 조심하자!
  • 단방향 매핑만으로도 이미 연관관계 매핑 완료된 상태이다.
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐이다.
  • 실무에서는 JPQL에서 역방향으로 탐색할 일이 많다.
  • 단방향 매핑을 잘 하고 양뱡항은 필요할 때 추가해도 된다.(테이블에 영향을 주지 않음)

📚 참고 및 자료 출처 : 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

profile
go

0개의 댓글