본문 바로가기
TIL

[231128] Spring 숙련주차 팀과제, JPA 연관관계

by 진진리 2023. 11. 28.
728x90

Spring 숙련주차 팀과제

팀 노션: https://www.notion.so/vanillacake369/424f22bf8bb34264a0e37038a443b2e8?v=d65de652cd584abcb44c3f63dd6b7c5e

 

HobiMate

A new tool for teams & individuals that blends everyday work apps into one.

www.notion.so

 팀 깃허브: https://github.com/sparta-are-you-t/hobby-bungae

 

GitHub - sparta-are-you-t/hobby-bungae

Contribute to sparta-are-you-t/hobby-bungae development by creating an account on GitHub.

github.com

 

소감

기본 요구사항 구현에 충실했고 테스트 코드까지 작성해서 테스트해봤으나 막상 postman으로 테스트해봤을 때 돌아가지 않았다.

그리고 entity 간의 다대다 관계가 2가지가 존재했고 아직 JPA 연관관계와 영속성, 양방향/단방향 등에 대한 개념이 확실히 잡혀있지 않다보니 이를 이용해 로직을 구현하는 과정에서 많은 어려움을 겪었다.

이렇게 팀 과제를 마무리한 것이 아쉬워서 혼자서 이번 팀 과제를 수정하고 JPA에 대해 더 공부해보고자 한다.

 


  • 무조건 다대다 관계로 구현?

개인 담당 튜터님과 짧게 면담을 진행하면서 튜터님께서 이번 팀 과제 코드를 잠깐 봐주셨는데 사용자인 User 엔티티와 취미인 Hobby 엔티티를 N:M이 아닌 1:N 관계로 구현하는 것이 더 좋지 않겠냐는 조언을 받았다.

지금까지는 실제로 N:M 관계이면 무조건 N:M 관계로 구현해야 한다는 생각만 가지고 있었는데 구현하고자 하는 기능에 따라 1:N 또는 N:1로 구현할 수도 있다는 것을 알게 되었다.

 

그래서 혼자서 팀 과제를 수정할 때는 다대다 관계를 최대한 줄여서 구현해봐야겠다는 생각이 들었다.


JPA 프로그래밍 책 참고

엔티티 매니저팩토리와 엔티티 매니저

  • 데이터베이스를 하나만 사용하는 경우 일반적으로 EntityManager Factory를 하나만 생성
  • 매니저 팩토리 생성
    • EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
    • METE-INF/persistemce.xml에 있는 정보를 바탕으로 EntityManagerFactory를 생성
  • 엔티티 매니저 생성
    • EntityManager em = emf.createEntityManager();
  • 엔티티 매니저 펙토리는 여러 스레드가 동시에 접근해도 안전하므로 공유 가능
  • 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 공유하면 안됨

 

연관관계 매핑

  1. 다대일(N:1) 속성
    • optional: 기본값 true. false일 경우 연관 엔티티가 항상 있어야 함
    • fetch: 기본 값 즉시로딩. 지연 로딩
    • cascade: 영속성 전이 기능 사용
  2. 단방향 연관관계
    • 회원 -> 팀 : 연관관계 설정
      •  Member.class
        • @ManyToOne
        • @JoinColumn(name="TEAM_ID")
        • private Team team; // 연관관계 매핑 
        • public void setTeam(Team team){ this.team = team; } // 연관관계 설정
    • 회원(Member) N : 1 팀(Team)을 저장하는 코드
      • Team team1 = new Team("team1", "팀1");
      • em.persist(team1);
      • Member member1 = new Member("member1", "회원1");
      • member1.setTeam(team1); //연관관계 설정
      • em.persist(member1);
    • 회원의 팀을 조회하는 코드(객체 그래프 탐색)
      • Member member = em.find(Member.class, "member1");
      • Team team = member.getTeam();
    • 회원의 팀을 수정
      • Member member = em.find(Member.class, "member1");
      • member.setTeam(team2);
    • 연관관계 삭제
      • Member member1 = em.find(Member.class, "member1");
      • member1.setTeam(null);
  3. 양방향 연관관계
    • 회원 <-> 팀 : 연관관계 설정
      • Team.class
        • @OneToMany(mappedBy = "team")
        • private List<Member> members = new AllayList<Member> ();
      • 팀에서 회원을 조회
        • Team team = em.find(Team.class, "team1");
        • List<Member> members = team.getMembers();
      • 데이터베이스 테이블은 외래 키 하나만으로 양방향 연관관계를 맺는다.
      • 엄밀히 말하면 객체에는 양방향 연관관계라는 것이 없다.
      • 연관관계의 주인: 외래 키를 관리할 수 있음: Member
      • 연관관계 저장
        • Team team1 = new Team("team1", "팀1");
        • em.persist(team1);
        • Member member1 = new Member("member1", "회원1");
        • member1.setTeam(team1); //연관관계 설정
        • team1.getMembers().add(member1);
        • em.persist(member1);
      • 양방향 관계 설정 코드를 묶어서 사용
        • Member.class
          • public void setTeam(Team team) { this.team = team; team.getMembers().add(this); }
      • 팀을 새로 설정할 때 기존 관계 제거 후 설정
        • if(this.team != null) { this.team.getMembers().remove(this); }
        • this.team = team;
        • team.getMembers().add(this);
    • 양방향 매핑 시 주의 사항: 무한루프
      • 서로 getTeam(), getMember()를 호출하는 경우 등
      • 엔티티를 JSON으로 변환할 때 자주 발생 -> 어노테이션을 제공
      • Lombok 라이브러리를 사용할 때도 자주 발생
  4. 우선 단방향 매핑을 사용하고 반대 방향으로 객체 그래프 탐색 기능이 필요할 때 양방향을 사용하도록 코드를 추가하는 방식으로!
    • @OneToMany 단방향의 문제점
      • 다른 테이블에 외래키가 있으면 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 한다.
      • 일대다 단방향보다는 다대일 양방향 매핑을 사용하자
      • 다대일 양방향(회원 Member <-> 팀 Team)
        • Member.class
          • public void setTeam(Team team) {
          •     this.team = team;
          •     if(!team.getMembers().contains(this)){ // 무한루프에 빠지지 않도록 체크
          •         team.getMembers().add(this);
          •     }
          • }
        • Team.class
          • public void addMember (Member member) {
          •     this.members.add(member);
          •     if(member.getTeam() != this) {   // 무한루프에 빠지지 않도록 체크
          •         member.setTeam(this);
          •     }
          • }
  5. 다대다 N:N
    • 연결 엔티티 사용
      • 비식별관계: 받아온 식별자는 외래 키로만 사용하고 새로운 식별자를 추가 -> 추천 !
      • 회원(N) - 주문 - 저장(N)
      • 저장하는 코드
        • em.persist(member1);
        • em.persist(productA);
        • Order order = new Order();
        • order.setMember(member1);
        • order.setProduct(productA);
        • em.persist(order);
      • 조회하는 코드
        • Long orderId = 1L;
        • Order order = em.find(Order.class, orderId);
        • Member member = order.getMember();
        • Product product = order.getProduct();