일기

2022-12-09(연관 관계)

SHsus 2022. 12. 9. 21:39

연관관계

지난주 시험이 너무 어이없게 진행이 되었었던 부분에 대해서 정리하는글

노션 내용에 아예 언급이 없었던 것은 아니지만 너무 생뚱맞게 나와서 블로그 글만을 보고 어찌저찌 만들었던

그 연관관계에 대한 내용을 정리해보는 글이다.

 

 

 

 

 

 

 

 

 

 

 

참고한 블로그 주소

 

[JPA] 다양한 연관관계 매핑 (1) 다대일, 일대다

엔티티의 연관관계를 매핑할 때는 3가지를 고려해야한다. 다중성 단방향, 양방향 연관관계의 주인 먼저 두 엔티티가 일대일 관계일지 일대다 관계인지 다중성을 고려한다. 다음으로, 두 엔티티

jgrammer.tistory.com

 

 

[JPA] 연관관계 매핑 기초

인프런 자바 ORM 표준 JPA 프로그래밍 - 기본편 강의 내용 정리 연관관계 매핑이란, 객체의 참조와 테이블의 외래 키를 매핑하는 것을 말한다. 연관관계는 다음 3가지를 고려해야한다. 1. 방향(Direct

seongho96.tistory.com

 

 

연관관계 란 ?

연관관계 매핑 이라는 말로 시작, 객체의 참조와 테이블의 외래 키를 매핑하는 것을 말한다.

 

 

방향

단방향과 양방향이 있다.
객체는 참조용 필드를 가지고 있는 연관된 객체를 조회할 수 있으므로 방향이 존재한다.
두 객체가 참조하는 관계를 양방향 관계, 한 객체에서 다른 객체만 참조하는 관계를 단방향 객체라 한다.

 

다중성

  1. 다대일 - @ManyToOne
  2. 일대다 - @OneToMany
  3. 일대일 - @OnteToOne
  4. 다대다 - @ManyToMany

보통 다대일과 일대다 관계를 많이 사용하고 다대다 관계는 실무에선 거의 사용하지 않는다고 한다.

이 부분은 기술매니저님들도 하나같이 똑같이 말씀해 주셨다.

 

연관과계 주인

객체는 양방향 참조가 존재하기 때문에 어느 쪽에서 외래키를 관리할지 정해야 한다.
외래키를 가진 테이블을 매핑한 엔티티에서 외래 키를 관리하는게 효율적이다.
따라서, 외래키를 가진 엔티티가 주인이라고 생각하면 쉽다.
일대다, 다대일 관계에서 항상 "다" 쪽이 외래키를 가진다. (주인이 아닌 쪽은 외래키를 변겨할수 없고 읽기만 가능)

 

 

 

 

다대일(N : 1) 단방향

 

 

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    ...
 
}

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

 

사용법

N : 1 의 연관관계를 맺을 때는 @ManyToOne 어노테이션을 사용하며,
외래키를 매핑하기 위해서는 @JoinColumn 어노테이션을 사용, 옵션 name 에 외래키 필드 이름을 넣는다.

 

정리

  • MEMBER 가 TEAM 을 참조한다 (O)
  • TEAM 이 MEMBER 를 참조한다 (X)

따라서, MEMBER 와 TEAM 은 다대일 단방향 관계이며,

외래키로는 MEMBER.TEAM 에서 관리한다.

 

 

 

 

 

 

 

 

 

 

 

다대일(N : 1) 양방향

 

 

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    public void setTeam(Team team) {
    	this.team = team;
        
        //무한 루프에 빠지지 않도록 체크
        if(!team.getMembers().contain(this)) {
        	team.getMembers().add(this)
        }
    
    ...
 
}

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();
    
    private String name;
    
    ...
    
}

 

정리

양방향은 외래키가 있는 쪽이 연관관계의 주인이다.
따라서, "다" 쪽의  Member 가 주인이 되고, 주인이 아닌 Team 은 mappedBy 를 사용한다.

 

양방향 연관관게는 항상 서로를 참조해야 한다.

이를 가능케 하려면 연관관계의 편의 메소드를 작성하는 것이 좋다.

예로, setTeam() 과 addMemeber() 이 같은 메소드일 때 편의 메소드를 어떤 엔티티에 작성해도 상관없지만,

양쪽에 다 작성할 시 무한루프에 빠지기 때문에 주의해야 한다.

 

 

 

 

 

 

 

 

 

 

일대다(1 : N) 단방향

 

 

@Entity
public class Member {

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

    ...
 
}

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    @OneToMany
    @JoinColumn(name = "TEAM_ID") //MEMBER테이블의 TEAM_ID(FK)
    private List<Member> members = new ArrayList<Member>();
    
    private String name;
    
    ...
    
}

 

정리

하나의 팀은 여러 회원을 참조하지만, 반대로 회원은 팀을 참조하지 않는 관계 이경우 일대다 단방향 연관관계이다.
이 경우에 Team 의 List.members 로 외래 키를 관리한다.
외래키는 "다' 쪽에서 관리하지만 단방향 관계로 "다" 쪽의 필드가 없어서 이런 상황이 발생했다.
이같이 일대다 관계를 매핑할 때에도 @JoinColumn 을 명시해야 한다.

 

일대다 단방향의 경우 매핑한 객체가 관리하는 외래키가 다른 테이블에 존재하게 된다.

이럴경우 본인 테이블에 있는 경우와 달리 연관관계를 통해 UPDATE SQL 를 추가적으로 실행해야 한다.

 

참고한 글에서 가져온 테스트코드 부분인다.

 

public void testSave()  {
    Member member1 = new Member("member1");
    Member member2 = new Member("member2");
    
    Team team1 = new Team("team1");
    team1.getMembers().add(member1);
    team1.getMembers().add(member2);
    
    em.persist(member1); //INSERT
    em.persist(member2); //INSERT
    em.persist(team1); // INSETRT + UPDATE

	transaction.commit();
insert into Member (MEMBER_ID, username) values (null, ?)
insert into Member (MEMBER_ID, username) values (null, ?)
insert into Team (TEAM_ID, name) values (null, ?)
update Member set TEAM_ID=? where MEMBER_ID=?
update Member set TEAM_ID=? where MEMBER_ID=?

 

위의 코드를 보면 Memeber 를 저장할 때, TEAM_ID 를 모르기 때문에 null 로 저장된다.

그리고 team1 을 저장할 때 앞서 저장한 Member 의 TEAM_ID 를 업데이트 한다.

 

그래서 일대다 단방향 매핑 보다는 다대일 양방향 매핑을 쓰는것이 좋다.

엔티티를 매핑한 테이블이 아닌 다른 테이블의 외래키를 관리한다는 것은 성능 문제도 있지만 관리가 문제다.

해결법으로는 다대일 양방향 매핑을 사용하면 된다.

 

 

 

 

 

 

 

 

 

일대다(1 : N) 양방향

 

 

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;
    
    private String username;
    
    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private Team team;
    

    ...
 
}

@Entity
public class Team {
    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    
    @OneToMany
    @JoinColumn(name = "TEAM_ID") //MEMBER테이블의 TEAM_ID(FK)
    private List<Member> members = new ArrayList<Member>();
    
    private String name;
    
    ...
    
}

 

정리

사실 일대다 양방향 매핑이라는 것은 존재하지 않는다. 그래서 다대일 양방향 매핑을 사용해야 한다.
이같은 관계에서 @OneToMany 는 주인이 될 수 없다.
관계형 데이터베이스의 특성상 일대다, 다대일 관계는 항상 "다" 쪽에 외래키가 존재한다.
따라서, 연관관계의 주인은 항상 @ManyToOne 이 된다.

 

사실, 일대다 양방향이 불가능한것은 아니다.

일대다 단방향 매핑 반대편에 같은 외래키를 사용하는 다대일 단방향 매핑을 읽기 전용으로 추가하면 된다.

하지만 이같이 하면 일대다 단방향 매핑의 단점을 그대로 가지기 때문에 사실 사용할 일은 없다.