[Spring_Boot] JPA - 연관관계 매핑(양방향), 연관관계의 주인

최현석·2022년 12월 8일
0

Spring_Boot

목록 보기
22/31

🧩 양방향 매핑

  • 현재 객체는 Member가 Team을 가졌으나, Team은 Member를 가지지 못한다.
  • 객체 참조와 외래키의 가장 큰 차이점
  • 테이블은 FK만 있으면 양쪽에 연관관계를 알 수 있다.
  • Member -> Team : N -> 1 => @ManyToOne
    • FK가 있는 곳
  • Team -> Member : 1 -> N => @OneToMany
    • FK가 거는 곳

Team

  • Team객체에 members라는 List를 추가해서 양방향 연관관계를 만들어준다.
    • team입장에서 member를 n개를 할당 받을 수 있어야 한다. 그래서 객체를 참조할 때 ArrayList 사용
	// @ManyToOne 부분 객체명과 매핑	
	@OneToMany(mappedBy = "team")
	private List<Member> member = new ArrayList<Member>();

JpaMain

  • 추가한 members를 확인하는 코드

    • 멤버에서 팀으로 접근 -> 팀에서도 멤버를 접근
			// 양방향 매핑
			Member findSideMember = em.find(Member.class, member.getId());
			// 멤버에서 
			List<Member> members = findSideMember.getTeam().getMember();
			
			for(Member m : members) {
				System.out.println("result = " + m.getName());
			}

값 수정

  • team_id에 7이 오지않고 변경한 1이 오게 된다.
			Team team = new Team();
			team.setName("TeamD");
			em.persist(team);
			
			Member member = new Member();
			member.setName("member4");
			member.setTeam(team);
			em.persist(member);
            
			// 수정
			//1L =1번에 시퀀스를 가진 값
			Team newTeam = em.find(Team.class, 1L);
			findMember.setTeam(newTeam);
			System.out.println("findTeamName : " + newTeam.getName());
			System.out.println("findTeam.getid() : " + newTeam.getId() );

테이블 연관관계

  • 관계 1개
  • Member 테이블 입장에서 Team 테이블 조인 가능
  • Team 테이블 입장에서 Member 테이블 조인 가능

객체 연관관계

  • 관계 2개
  • Member 객체에서 Team 객체로 연관관계 1개(단방향)
  • Team 객체에서 Member 객체로 연관관계 1개(단방향)
  • 사실은 단방향 연관관계가 2개 있는 것이다.

관리의 딜레마

  • 둘 중 하나로 외래키를 관리해야 한다.
  • 객체의 입장
    • Member에서 Team으로 가는 team 참조 값과, Team에서 Member로 가는
      members 참조 값이 있다.
    • Member에서 Team값이 수정 되었을 때 Member table의 TEAM_ID가 수정되야 하는지,
      Team에 있는 members를 수정했을 때 Member table의 TEAM_ID가 수정되야 하는지?
  • DB의 입장
    • Member table에있는 TEAM_ID만 update되면 된다 -> 룰(주인)이 생긴다.

🧩 연관관계의 주인(Owner) - 양방향 매핑 규칙

  • 객체의 두 관계중 하나를 연관관계의 주인으로 지정
  • 연관관계의 주인만이 외래 키를 관리(등록, 수정)
  • 주인이 아닌쪽은 읽기만 가능
  • 주인은 mappedBy 속성 사용X
    -> mappedBy : 내가 누군가에 의해서 mapping되었다 라는 뜻
  • 주인이 아니면 mappedBy 속성으로 주인 지정

Member

  • 1(Team) 대 N(다)(Member)
  • @ManyToOne : 여기에선 Team이 하나
  • @JoinColumn(name = "TEMA_ID") : 관계 컬럼을 적어준다. Team_ID와 조인해야 한다.
  • 외래키가 있는 객체가 주인
    @ManyToOne
	@JoinColumn(name = "TEAM_ID")
	private Team team;

Team

  • team에 의해서 관리가 된다.
  • mappedBy가 적힌 곳은 읽기만 가능하다.
  • 값을 넣어봐야 아무일도 벌어지지 않는다.
  • 대신 조회는 가능
	@OneToMany(mappedBy = "team")

  • 여기서는 Member.team이 연관관계의 주인
  • 결론: 외래키가 있는곳을 주인(Owner)로 결정하라.

🧩 양방향 연관관계의 주의

  • 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자
  • 연관관계 편의 메소드를 생성하자
  • 양방향 매핑시에 무한 루프 조심하자
    -> toString(), lombok lib -> @data 조심할 것

JpaMain2

  • 양방향 매핑시 가장 많이 하는 실수
			Member member = new Member();
			member.setName("member1");
			em.persist(member);
			
			Team team = new Team();
			team.setName("TeamA");
			team.getMember().add(member);
			em.persist(team);
            
            em.flush();
            em.clear();

  • Team이 없는 Member가 들어가있다.
  • 연관관계 주인은 Member다.
  • 그래서 팀을 통해 멤버의 값을 add 업데이트 해주는 것은 불가

1) Team으로 가지고 올 때

			Team team = new Team();
			team.setName("TeamA");
			em.persist(team);
			
			Member member = new Member();
			member.setName("member1");
			member.setTeam(team);
			em.persist(member);
            
            em.flush();
            em.clear();
            
            System.out.println("===========================");
			
			Team findTeam = em.find(Team.class, team.getId());
			List<Member> members = findTeam.getMember();
			
			for( Member m : members) {
				System.out.println("m = " + m.getName());
			}
			
			System.out.println("===========================");
  • select Team
  • select Member
    • team안에 member를 넣어준다.
  • 결과값이 출력된다.

2) Team으로 가지고 올 때 em.flush(); em.clear()없다면

  • team.getMembers().add(member); , member.setTeam(team);

    • 객체 지향적인 입장에서 양쪽에 모두 값을 넣어주어야 한다.
    • 양방향 매핑시에는 양쪽에 값을 모두 입력해 주어야 한다.
    • DB를 다시 다녀오지 않고 객체 상태로만 사용 할 수 있다.
  • em.flush(); em.clear()없다면 출력되지 않는다.
    Team에는 Member값이 없다. -> Member에는 Team값이 있다
    -> 이 상태에서 team을 기준으로 Member 획득하려고함 -> Db를 들리지 않고 영속성 컨텍스트에 값을 가져온다 -> Team.class를 기점으로해서 획득해온 결과값에는 Member객체 값이 담겨있지 않다. -> 그 상태로 findTeam.getMember(); 하면
    -> 출력해 줄 것이 없다

			// Team에는 Member값이 없다.
			Team team = new Team();
			team.setName("TeamA");
			em.persist(team);
			
            // Member에는 Team값이 있다
			Member member = new Member();
			member.setName("member1");
			member.setTeam(team);
			em.persist(member);
            
            // 객체 지향적인 입장에서 양쪽에 모두 값을 넣어주어야 한다.
			// 양방향 매핑시에는 양쪽에 값을 모두 입력해 주어야 한다.
			// DB를 다시 다녀오지 않고 객체 상태로만 사용 할 수 있다.
            team.getMember().add(member);
            
            System.out.println("===========================");
			
            //Team.class 획득해온 결과값에는 Member객체 값이 담겨있지 않다.
			Team findTeam = em.find(Team.class, team.getId());
			List<Member> members = findTeam.getMember();
			
            // 출력해 줄 것이 없어
			for( Member m : members) {
				System.out.println("m = " + m.getName());
			}
			
			System.out.println("===========================");

3) team.getMember().add(member); 을 넣지 않는다면

  • team.getMember().add(member) = team.getMember().add(this);

JpaMain2

  • team.getMember().add(member);Member, Team 에서 적용
			Team team = new Team();
			team.setName("TeamA");
			em.persist(team);
			
			Member member = new Member();
			member.setName("member1");
		 // 	member.setTeam(team);
            member.changeTeam(team); // Member에 적용 
			em.persist(member);
            
            team.addMember(member); // Team에 적용 
            
            // team.getMember().add(member);

Member

  • @Setter(value = AccessLevel.NONE)
    • lombok 에서 자동 setter 생성을 막아준다.
  • member.changeTeam(team);
	@ManyToOne
	@JoinColumn(name = "TEAM_ID")
	@Setter(value = AccessLevel.NONE) 
	private Team team;
    
    public void setTeam(Team team) {
		this.team = team;
	}
    ====>>>  changeTeam으로 바꿔준다. 
    // 일반적으로 setter의 형태가 아니면 메서드 이름을 바꿔준다.
	// 추후 소스코드를 봤을때 단순 setter의 작업이 아닌 중요한 작업을 진행하는지를 파악 할 수 있다.
	public void changeTeam(Team team) {
		this.team = team;
		// this : 나 자신의 인스턴스를 넣어준다.
		team.getMember().add(this);
	}

Team

  • team.addMember(member);
	public void addMember(Member member) {
		member.setTeam(this);
		this.member.add(member);
	}

양방향 매핑 정리

  • 단방향 매핑만으로도 이미 연관관계 매핑은 완료
  • 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐
  • 양방향 사용 이유
    • JPQL에서 역방향으로 탐색할 일이 많음
    • 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨 (테이블에 영향을 주지 않는다.)
  • 결론 : 객체 입장에서 양방향 매핑은 필수는 아님, 필요 시에 그때 생성해도 늦지않다.

연관관계의 주인을 정하는 기준

  • 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안된다.
  • 연관관계의 주인은 외래 키의 위치를 기준으로 정해야 함.

0개의 댓글