🙏내용에 대한 피드백은 언제나 환영입니다!!🙏
객체들간의 관계가 필요한 경우가 있다.
예를 들면, 팀과 멤버, 사용자와 사용자가 작성한 게시글, 게시글과 그 게시글에 달린 댓글 등이 있다.
이러한 관계를 위하여 아래의 코드를 기반으로 연관관계 매핑에 대해서 알아보겠다.
(연관관계를 하지 않았다는 가정으로 teamId 필드를 사용)
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Team { // Team Entity
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Member { // Member Entity
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Long teamId;
}
❕스프링 데이터 JPA로 엔티티 객체에 대한 CRUD는 JpaRepository를 상속하였다는 가정을 하겠다.
우선 팀과 멤버를 저장하는 코드를 작성해보자.
Team team = new Team();
team.setName("Spring");
TeamRepository.save(team);
Member member = new Member();
member.setName("홍길동");
member.setTeamId(team.getId());
MemberRepository.save(member);
연관관계 매핑을 하지않은 상태에서 멤버가 속한 팀을 찾는 코드를 작성해보자.
Member findMember = MemberRepository.findById(member.getId()).orElse(null);
Team findTeam = TeamRepository.findById(findMember.getTeamId()).orElse(null);
위와 같이 멤버를 찾은 후 멤버에 저장된 팀의 id를 가지고와 다시 Team의 객체를 찾는 과정을 거친다.
이러한 코드는 객체간 제대로 상호작용하지 않아 코드의 복잡성을 보여주며, 객체지향적인 모습을 보여주지 않는다.
그래서, 사용하는 것이 연관관계 매핑이다.
객체간 관계 매핑을 할 때, 단방향 연관관계, 양방향 연관관계가 있다.
객체들간에는 단반향만 가능하다. 그래서, 양방향 연관관계는 단방향이 2개인 것이다.
(ex. A->B 단방향, B->A 단방향)
연관관계를 위해서 사용되는 어노테이션은
관계의 유형에 맞게 @OneToMany(1:N관계), @OneToOne(1:1관계), @ManyToOne(N:1관계), @ManyToMany(N:M관계) 중에서 선택하면 되고,
@JoinColumn을 통해서 외래키를 지정한다.
@JoinColumn은 생략이 가능하다. 생략하게 되면 외래 키를 찾을 때 기본 전략을 사용한다.
기본 전략: 필드명_참조하는 테이블의 기본 키 컬럼명
그러면 단방향 연관관계를 이용해 Team과 Member 엔티티에 양방향 연관관계로 설정해보겠다.
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Team { // Team Entity
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
@Getter
@Setter
@NoArgsConstructor
@Entity
public class Member { // Member Entity
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
위 코드에서 Team Entity에는 1:N관계인 @OneToMany를 사용해 Member과 연관관계를 맺었다.
그리고, Member Entity에는 N:1관계인 @ManyToOne을 사용해 Team과 연관관계를 맺었다.
이렇게, Team -> Member || Member -> Team 을 하여 양방향으로 연관관계를 맺었다.
양방향으로 연관관계를 맺고자 한다면 주인이 있어야 한다.
연관관계에서 주인이란 저장, 수정, 삭제 등의 제어의 권한을 갖는 것이다.
주인을 정할 때는 외래 키가 있는 곳을 연관 관계의 주인으로 하면 된다.
위에서는 FK(Foreign Key)가 Member에 있으므로 Member에 있는 team을 주인으로 설정하였다.
mappedBy 속성은 주인이 아닌 쪽에 설정해주면 된다. 즉, Team에 사용하면 되고, 위 코드에서는 Team에서 @OneToMany(mappedBy = "team")를 지정해주며 Team이 주인이 아님을 설정해 주었다.
💡 mappedBy에 쓰는 것은 주인의 필드명이다.
💡 위에서 말했듯, @JoinColumn에는 필드명_id이다. (id는 참조하는 테이블의 기본 키)
Team team = new Team();
team.setName("Spring");
TeamRepository.save(team);
Member member = new Member();
member.setName("홍길동");
member.setTeam(team);
MemberRepository.save(member);
(위 코드에서 달라진점 : team의 id를 넣는 것이 아닌 team을 넣어줌.)
Member findMember = MemberRepository.findById(member.getId()).orElse(null);
Team findTeam = findMember.getTeam();
연관관계를 하지 않은 상태에서는 member을 가지고와 member에 저장된 teamid를 통해 다시 Team의 정보를 가지고 오는 형식을 하였다.
연관관계를 한 후, 위 코드와 같이 member을 찾아와 member에 저장되어 있는 team을 가지고 올 수 있다.
연관관계에 대해 알아보며 생각한 장점에 대해서 몇 가지 적어 보겠다.
모든 연관관계에 대해 서로를 양방향으로 오히려 복잡해질 수 있다.
블로그를 예로 들어 보겠다.
User(사용자), Post(게시글), Comment(댓글)의 관계에서
User <-> Post, User <-> Comment 형식으로 양방향 관계를 맺는다고 한다면,
User에는 굉장히 많은 엔티티와 연관 관계를 맺게 된다. 그리고, 그리고 다른 엔티티들도 불필요한 연관관계 매핑으로 인해 복잡성이 증가할 수 있다. (Comment의 User를 찾는 과정에서 Post까지 나와 불필요한 query 생성 - LAZY 처리 안할 시.)
그래서, 상황에 맞게 단방향, 양방향 관계를 맺어야 한다.
연관관계에 대해 글을 작성하며 불필요한 쿼리가 생성되고, 원하지 않을 때 정보를 가지고 오는 등 몇가지 복잡성을 높이는 문제점이 있다는 것을 다시 한 번 인지하였다.
이러한 문제점을 다루는 내용을 후에 작성해보고자 한다.