개발하는 쿠키
article thumbnail

단방향 연관관계

회원테이블과 팀 테이블이 있다.

회원은 하나의 팀에만 속할 수 있고, 1개의 팀에는 여러 명의 회원이 있다.


Member : Team = 다 : 1 관계이다.
Member가 다고,
Team이 1이다.

객체 연관관계

회원 객체, 팀 객체는 단방향 관계다.

Member.team : Member 객체에서 Team객체는 참조 가능하다. 

Team.member : Team객체에서 Member객체는 참조할 수 없다.

 

테이블 연관관계

회원 테이블, 팀 테이블은 양방향 관계다.

회원 테이블의 TEAM_ID로 팀 테이블 조인 가능하다.

MEMBER JOIN TEAM, TEAM JOIN MEMBER 가 가능하므로 양방향 관계이다.

 

객체 연관관계와 테이블 연관관계의 가장 큰 차이

객체는 클래스에 다른 클래스 필드를 참조해야하기때문에 양방향으로 만들고 싶으면,

서로 다른 단방향 관계 2개를 만들어야 한다.

 

순수한 객체 연관관계

객체 그래프 탐색: 객체를 참조해서 연관관계를 탐색한다.

Team findTeam = member1.getTeam();

 

테이블 연관관계

외래키와 조인을 사용해서 연관관계를 탐색할 수 있다.

 

객체 관계 매핑

객체와 테이블의 서로 다른 연관관계를 매핑할 수 있다.

 

@Entity
public class Member{
    @Id
    @Column(name="MEMBER_ID")
    private String id;
    
    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private Team team;
}

 

@Entity
public class Team{
    @Id
    @Column(name="TEAM_ID")
    private String id;
}

 

@JoinColumn

외래키를 매핑할 때 사용한다.

 

@ManyToOne

다대일 관계에서 사용한다.

 

연관관계 사용

저장

member1.setTeam(team1);
em.persist(member1);

조회

1. 객체 그래프 탐색

객체를 통해 연관된 엔티티를 조회하는 것

Member member = em.find(Member.class, "member1");
Team team = member.getTeam();

2. 객체지향 쿼리 사용

// JPQL을 사용한 예시
SELECT m 
FROM MEMBER m JOIN m.team t
WHERE t.name = teamName

수정

Member member = em.find(Member.class, "member1");
member.setTeam(team2);

불러온 엔티티 값만 수정해두면 변경 감지 기능이 동작하여 자동으로 데이터베이스에 UPDATE 쿼리문이 날아간다. 

따라서 em.update() 같은 메소드를 쓸 필요가 없다.

 

연관관계 제거

member.setTeam(null);

연관된 엔티티 삭제

연관된 엔티티를 삭제하려면 연관관계를 제거하고 삭제해야 외래 키 제약조건이 발생하지 않는다.

member.setTeam(null);
em.remove(team);

양방향 연관관계

양방향 객체 연관관계

 

@Entity
public class Member{
    @Id
    @Column(name="MEMBER_ID")
    private String id;
    
    @ManyToOne
    @JoinColumn(name="TEAM_ID")
    private Team team;
}
@Entity
public class Team{
    @Id
    @Column(name="TEAM_ID")
    private String id;
    
    // 추가되는 부분
    @OneToMany(mappedBy = "team") // Member class 에서 사용되는 매핑 필드 값
    private List<Member> members = new ArrayList<Member>();
}

일대다 컬렉션 조회

Team team = em.find(Team.class, "team1");
List<Member> members = team.getMembers();

연관관계 주인

엔티티를 양방향 관계로 설정하면 객체의 참조는 2개, 외래키는 1개다.

이로 인해 생기는 문제점이 있다.


1. 일관성 유지 어려움
객체 참조가 2개이기 때문에 양쪽에서 한 쪽 엔티티를 수정할 때 다른 쪽 엔티티의 값도 변경해줘야 한다.

2. 무한루프
@ToString같은 어노테이션의 경우 양쪽 객체에 붙어있으면 무한루프가 발생한다.

3. 성능 문제
데이터베이스 쿼리를 작성할 때 조인을 사용하게 되면 사용하지 않는 불필요한 데이터까지 가져오게 된다.
이때 성능 문제가 생길 수 있다.

따라서 어떤 객체의 참조가 외래키를 관리할지 정해야 하는데 이를 연관관계의 주인이라고 한다.

 

객체의 참조

회원 -> 팀

팀 -> 회원

 

외래키

TEAM_ID

 

양방향 매핑의 규칙: 연관관계의 주인

연관관계 주인은 외래 키를 등록, 수정, 삭제할 수 있다.

주인이 아닌 클래스는 mappedBy 속성을 사용해서 주인을 지정해야 한다.

 

연관관계의 주인은 외래 키가 있는 곳

연관관계 주인은 외래키가 있는 테이블을 관리하는 클래스로 정해야 한다.

항상 @ManyToOne 쪽이 연관관계의 주인이 되므로 mappedBy 속성이 없다.

 

@ManyToOne 관계에서 연관관계 주인이 One인 경우 발생하는 문제점 (회원 - 팀 관계에서 연관관계 주인이 팀일 때 발생하는 문제점)
1. Cascade 문제
연관관계 주인인 팀을 삭제할 경우 원치 않은 회원들까지 모두 삭제될 수 있어서 주의가 필요하다.

2. 데이터베이스 성능 최적화 문제
팀을 조회하면서 항상 모든 회원들이 조회될 경우, 회원 수가 많으면 성능 문제가 발생할 수 있다.

 

양방향 연관관계 저장

// 연관관계의 주인이 아니기 때문에 무시된다.
team1.getMembers().add(member1);

// 연관관계가 설정된다.
member1.setTeam(Team1);

양방향 연관관계의 주의점

순수한 객체까지 고려한 양방향 연관관계

객체 관점에서는 양쪽 방향에 모두 값을 입력해주는 것이 안전하다.

그래야 애플리케이션 레벨에서 team1에서도 member 목록을 가져올 수 있고, member1에서도 team 정보를 가져올 수 있다.

public void Relation () {
    //팀1 저장
    Team team1 = new Team();
    em.persist(team1);
	
    Member member1 = new Member();
       
    //양방향 연관관계 설정
    member1.setTeam(team1) ; //연관관계 설정 member 1 -> team1
    team1.getMembers().add(member1); //연관관계 설정 team1 -> member 1
    em.persist(member1);
}

연관관계 편의 메소드

양방향 연관관계를 고려해서 set을 양쪽 다 하다 보면 실수할 수 있다.

따라서 1개의 set()메소드에서 양방향 연관관계를 설정하는 것이 좋다.

@Entity
public class Member{
    ...
    public void setTeam(Team team) {       
        //양방향 연관관계 설정
        this.setTeam(team) ;
        team.getMembers().add(this);
    }
    ...
}

연관관계 편의 메소드 작성 시 주의사항

member1.setTeam(teamA);
member1.setTeam(teamB);

위와 같은 코드처럼 member1을 teamA 로 세팅하면 member1 -> teamA , teamA -> member1 와 같이 양방향 관계가 생긴다.

그리고 member1을 teamB로 세팅하면 member1 -> teamB , teamB -> member1 관계가 생긴다. 이 때 teamA -> member1 관계는 삭제되지 않는다.

양방향 매핑 삭제되지 않은 관계

따라서 기존 연관관계를 다른 연관관계로 교체할 때 기존 연관관계를 깨끗하게 없애야 한다.

@Entity
public class Member {
    private Team team;
    ...
    public void setTeam(Team team) {
        // 기존 관계를 제거
        if(this.team != null){
            this.team.getMembers().remove(this);
        }
        this.team = team;
        team.getMembers().add(this);
    }
}

정리

- 단방향 매핑만으로 연관관계 매핑은 완료된다.

- 양뱡향 연관관계를 사용할 때는 양쪽 객체에 필드 값을 입력해야 하고, 다른 연관관계로 교체할 때 기존 연관관계를 깨끗하게 없애야 하므로 필요할 때만 양방향 연관관계를 사용해야 한다. 

- 연관관계 주인은 외래 키가 있는 곳으로 정해야 한다.

- 비즈니스 중요도로 접근하면 안 된다. 차 클래스, 바퀴 클래스가 있을 때 비즈니스적으로는 차가 더 중요하지만 객체적으로는 바퀴가 차 클래스의 외래 키를 가지고 있기 때문에 바퀴가 연관관계의 주인이 된다.

- 양방향 매핑에서 @ToString 무한루프에 빠지지 않도록 주의해야 한다.

 


5장 실습예제 코드는 https://github.com/mjkim103301/jpa-practice 여기를 참고하면 됩니다.


참고자료

- https://www.yes24.com/Product/Goods/19040233 "자바 ORM 표준 JPA 프로그래밍-김영한"

반응형
profile

개발하는 쿠키

@COOKIE_

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!