객체는 참조
를 사용하여 연관관계
를 맺고 테이블은 외래 키
를 사용하여 연관관계
를 맺는다.
관계가 있는 다른 데이터를 참조한다는 점에서는 동일하지만 방향과 연관관계의 주인이라는 특징에서 참조
와 외래 키
는 다른 특징을 갖는다.
위과 같은 객체 모델링이 있을 때, 연관관계가 없는 경우의 코드는 다음과 같다.
Member Entity
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@Column(name = "user_name")
private String name;
@Column(name = "team_id)
private Long teamId;
}
Team Entity
@Entity
@Getter @Setter
public class Team {
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
}
생성매서드
// 생략
//==생성 메서드==//
public void createMember(Member member) {
Team team = new Team();
team.setName(member.getName());
em.persist(team);
Member member = new Member();
member.setName(member.getName());
member.setTeamId(team.getId());
em.persist(member);
}
이 상황에서 해당 입력된 구성원의 소속 팀을 찾기 위해서는 Member
객체의 member
를 찾고
해당 Team
객체에서 id
를 조회하여야 하는데, 이는 객체지향스럽지 않은 자원이 상대적으로 많이 사용되는 코드이다.
Long findTeamID = em.find(Member.class, member.getId()).getId();
Team findTeam = em.find(Team.class, findTeamId);
방향에는 단방향
과 양방향
이 있다.
테이블
에는 외래 키(FK)
를 통한 조인(join)
으로 양방향 쿼리가 가능하여, DB
에는 방향의 개념이 없다.
SELECT * FROM MEMBER M JOIN TEAM T ON M.TEAM_ID = T.TEAM_ID;
SELECT * FROM TEAM T JOIN MEMBER M ON M.TEAM_ID = T.TEAM_ID;
하지만 객체
의 경우에는 참조용 필드를 가지고 있는 객체만 연관된 객체를 조회할 수 있기에, 방향
이라는 개념이 존재한다.
2개의 객체가 양방향관계를 맺을 때는, 연관관계의 주인을 지정해야 한다.
보통, 외래 키를 가진 테이블과 매핑되는 엔티티가 외래 키를 가지는 것이 효율적이므로, 보통은 외래키를 가진 엔티티를 연관관계의 주인으로 지정한다.
🚨 외래 키를 관리하는 연관관계의 주인만이 외래 키를 변경할 수 있으며, 주인이 아닌 곳은 읽기만 가능다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "team_id");
private Long id;
}
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
...
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "team_id");
private Long id;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
@OneToMany(mappedBy = "team")
를 추가하여 연관관계의 주인이 Member.team
임을 지정해준다.
@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
...
}
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "team_id");
private Long id;
@OneToMany
@JoinColumn(name = "team_id")
private List<Member> members = new ArrayList<>();
}
🚨 문제 !!
일대다 단방향 매핑은 매핑한 객체가 관리하는 외래 키가 다른 테이블에 있다.
때문에, INSERT SQL 외에 UPDATE SQL이 추가로 발생하게 된다.
즉, Member
는 Member
에 매핑되어 문제가 되지 않으나 Team
의 FK
를 저장할 방법이 없기 때문에 조인 및 UPDATE SQL을 날려야한다.
Member
를 수정했는데 Team
이 수정이 됨.일대다(1:N) 단방향 연관 관계 매핑이 필요한 경우는 그냥 다대일(N:1) 양방향 연관 관계를 매핑해버리는게 추후에 유지보수에 훨씬 수월하기 때문에 이 방식을 채택하는 것이 좋다.
@JoinColumn(updatable = false, insertable = false)
키워드만 추가 된다.@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
@ManyToOne(@JoinColumn(updatable = false, insertable = false)
...
}
🚨 일대다 단방향과 동일한 문제를 가지므로 다대일 양방향 매핑을 사용하는 것을 권장함.
@Entity
@Getter
public class Post {
@Id @GeneratedValue
@Column(name = "post_id")
private Long id;
@Column(name = "title")
private String title;
@OneToOne
@JoinColumn(name = "attach_id")
private Attach attach;
}
@Entity
@Getter
public class Attach {
@Id @GeneratedValue
@Column(name = "attach_id")
private Long id;
private String name;
}
일대일 단방향(대상 테이블에 FK)
지원 안함 ❌
일대일 양방향(대상 테이블에 FK)
일대일 양방향
은 주 테이블과 대상 테이블의 위치만 바뀐 것으로 위와 동일하게 처리하면 된다.
📢 여기서 잠깐! "외래 키를 어디서 관리하는게 좋을까는 고민이 필요!!"
테이블은 한 번 생성되면 변경되기 어렵지만, 비즈니스는 쉽게 바뀔 수 있다.
위의 예에서 1개의 글에는 1개의 첨부만 허용 했으나 1개의 글에 복수의 첨부파일을 허용하는 경우
이 때는 Attach
에 외래 키를 관리하는 것이 변경에 유연해진다.
하지만, 주 테이블(Post
)에 외래 키를 관리하는 경우에는 성능상 이점이 생긴다.
🚨 실무에서 사용하지 않는 것을 권장함.(실무 사용 금지 )
사용해야 한다면, @OneToMany - @ManyToOne으로 풀어서 사용할 것!!
tstory - 정아마추어 코딩블로그
tstory - ShinD님 블로그
개인적으로 공부하며 기록한 내용으로, 틀린 내용이 있는 경우 덧글을 달아주시면 감사하겠습니다. 😍