JPA 연관관계 매핑의 종류
JPA 연관관계 매핑의 종류는 @ManyToOne, @OneToMany, @OneToOne, @ManyToMany가 있다.
이 글에서는 @ManyToOne과 @OneToMany 관계에 대해 다룬다.
아래 예제에서는 Team에 속해있는 여러 Member들을 가정했다.
@ManyToOne(다대일 매핑)
가장 많이 사용하는 연관관계로 여러 개(Many)의 멤버가 한 개(One)의 팀에 속해있는 것을 생각하면 된다.
Member가 Team의 ID를 FK로 갖게 된다.
이러한 관계에서 연관관계의 주인은 Member이다.
@Entity
public class Member {
@Id @GeneratedValue
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;
}
이렇게 설계하면 Member.getTeam()으로 멤버가 속해있는 팀의 정보를 불러다 사용할 수 있고,
이는 내부적으로 Team의 TEAM_ID 칼럼이 Member의 FK로 등록되게 된다.

Member 객체는 Team team을 필드로 갖지만, MEMBER 테이블에서는 TEAM_ID를 칼럼으로 갖게 된다.
@ManyToOne -> @JoinColumn(name = "TEAM_ID")로 지정해서 TEAM의 TEAM_ID 칼럼과 매핑할 수 있는 것이다.
여기까지가 기본적인 매핑이다. 그러나 편의를 위해 Team 객체 쪽에서도 Team에 속한 멤버의 리스트를 조회할 수 있도록 할 수 있다.
다대일 양방향 매핑 - @OneToMany(mappedBy = "xxx")
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
아래와 같이 @OneToMany 어노테이션을 통해 반대로 Team 쪽에서 members 리스트를 필드로 가질 수 있다.
(mappedBy = "team") 속성은 꼭 추가해야 한다. Member의 "team" 필드에 의해 매핑되었다는 의미이다.
List<Member> memberList = teamA.getMembers();
위와 같이 Team 객체에서 getter 메서드로 members 리스트를 불러다 사용할 수 있다.
그러나 이것은 테이블과는 아무런 관련이 없는 기능이다.
이 members라는 필드는 자바 코드에서만 편의를 위해 사용하려고 만드는 부가적인 기능일 뿐이다.
Member member = new Member();
teamA.getMembers().add(member);
위와 같이 teamA에서 getMembers()로 teamA에 속한 memberList를 불러와서 그 list에 새로운 멤버를 추가해도
JPA 영속성 컨텍스트와 DB의 테이블에는 아무런 영향도 생기지 않는다.
즉, 이 members 필드는 오직 조회만을 위한 필드이다.
여전히 해당 팀에 속한 멤버를 추가하려면 멤버를 만들고, member.setTeam(teamA) 로 추가해야 한다.

위의 그림과 같이 객체의 연관관계에서는 Team.members의 OneToMany 연관관계가 추가되었지만,테이블 상에서는 아무런 변화가 없는 것을 알 수 있다. 그러나 또 다른 문제가 있다.JPA의 쓰기 지연 때문에 memberC.setTeam(teamA)를 실행해도 해당 트랜잭션이 커밋되기 전까지는
teamA.members에 memberC가 포함되지 않을 수 있다.
따라서 @OneToMany(mapped = "team")으로 멤버 리스트를 사용하려 할 때는 추가적으로 편의 메서드를 생성하여 이 부분까지 관리하는 것이 좋다.
편의 메서드 생성
@Entity
public class Member {
//생략
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
}
위와 같이 테이블과는 아무 관련이 없지만 team.getMembers(). add(this) 코드를 추가함으로써 예기치 못한 오류를 방지할 수 있다. 여기서 this는 Member 객체 자신을 나타낸다.
( 기본 setter인 setTeam과의 구분을 위해 메서드 명을 changeTeam으로 설정했다. )
결론
@ManyToOne은 가장 많이 사용하는 기본적인 매핑 방법이고, 가장 권장되는 방법이다.
@ManyToOne 매핑 시에 추가로 @OneToMany(mappedBy = "xxx")을 통해 양방향 매핑으로 설정할 수 있지만, 이 부분은 항상 사용하길 권장되진 않는다.
꼭 필요할 때만 사용하면 좋을 것 같다. (Team에 속한 멤버 리스트 데이터가 자주 필요한 경우 등)
@OneToMany(일대다 매핑)
Team과 Member 사이의 OneToMany 매핑도 사실 현실에서 생각해 보면 똑같은 관계이다.
하나(One)의 Team에 여러 명(Many)의 Member가 속해있는 것이다.
Table에서도 똑같다. 여러 Member는 TEAM_ID를 FK로 가지게 된다.
그러나 객체 세상에서만 조금 다르다.
Team의 members 필드에서 Member를 관리하고, Member 객체에서는 Team을 알 수 없다.
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
}
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
}
이렇게 설계하면 Member 객체에서는 자신이 속한 Team의 정보를 알 수 없고, Team에서만 Member 리스트를 알 수 있다.
이 매핑에서 연관관계의 주인은 Team이 된다.
테이블에서 보자면 Team.getMembers(). addMember(memberA);를 실행했을 때,
Team 테이블에는 변화가 없고 MEMBER의 TEAM_ID를 UPDATE 하는 쿼리가 날아간다.

일대다 단방향 매핑의 단점
이 경우에는 Member객체에는 TEAM관련 속성이 없지만, MEMBER 테이블에는 TEAM_ID(FK)가 존재하게 된다.
즉, 엔티티가 관리하는 FK가 다른 테이블에 존재하게 된다.
또한 Team을 건드렸는데 Member에 대한 UPDATE SQL 나간다.
뭔가 자연스럽지 못하고 객체와 테이블의 불일치가 존재한다.
따라서 이 방법은 권장하지 않는 방법이다.
특별한 이유가 없다면 Member에 @ManyToOne으로 Team 필드를 갖게 하는 방식을 사용하는 게 좋을 것 같다.
그리고 Team에서 members가 필요하더라도 ManyToOne 양방향 매핑을 사용하는 게 더 나은 방법이다.
일대다 양방향 매핑 (공식적 x)
이 방법은 공식적으로 제공하는 방식이 아니다. 꼭 필요하지 않으면 사용하지 않는 것을 권장한다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) //읽기 전용으로 설정
private Team team;
}
Member 객체에 Team 필드를 추가하여 ManyToOne 연관관계의 주인처럼 만들고,
JoinColumn에서 쓰기 기능을 사용하지 못하게 만든다. ( insertable = false, updatable = false )
이렇게 하면 Member에서 Team을 조회하는 기능을 추가할 수 있다.

이 방법은 딱 봐도 이유 없이 복잡해지고 억지스러운 부분이 많다.
다대일 단방향을 이용하고, Team에서 members가 필요하다면 다대일 양방향 매핑을 이용하자!
(참고) 인프런 - 김영한 님 '자바 ORM 표준 JPA 프로그래밍 - 기본 편'
https://www.inflearn.com/course/ORM-JPA-Basic
'JPA & QueryDSL' 카테고리의 다른 글
[JPA] 연관관계 매핑 (1) | 2023.10.06 |
---|---|
[JPA] 연관관계 매핑 - @OneToOne 일대일 매핑 (0) | 2023.10.06 |
[JPA] @Entity - 필드와 컬럼 매핑의 여러가지 속성 (1) | 2023.10.04 |
[JPA] @Entity - 기본 키 매핑 (1) | 2023.10.04 |
[JPA] 엔티티 매핑 (0) | 2023.10.04 |
JPA 연관관계 매핑의 종류
JPA 연관관계 매핑의 종류는 @ManyToOne, @OneToMany, @OneToOne, @ManyToMany가 있다.
이 글에서는 @ManyToOne과 @OneToMany 관계에 대해 다룬다.
아래 예제에서는 Team에 속해있는 여러 Member들을 가정했다.
@ManyToOne(다대일 매핑)
가장 많이 사용하는 연관관계로 여러 개(Many)의 멤버가 한 개(One)의 팀에 속해있는 것을 생각하면 된다.
Member가 Team의 ID를 FK로 갖게 된다.
이러한 관계에서 연관관계의 주인은 Member이다.
@Entity
public class Member {
@Id @GeneratedValue
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;
}
이렇게 설계하면 Member.getTeam()으로 멤버가 속해있는 팀의 정보를 불러다 사용할 수 있고,
이는 내부적으로 Team의 TEAM_ID 칼럼이 Member의 FK로 등록되게 된다.

Member 객체는 Team team을 필드로 갖지만, MEMBER 테이블에서는 TEAM_ID를 칼럼으로 갖게 된다.
@ManyToOne -> @JoinColumn(name = "TEAM_ID")로 지정해서 TEAM의 TEAM_ID 칼럼과 매핑할 수 있는 것이다.
여기까지가 기본적인 매핑이다. 그러나 편의를 위해 Team 객체 쪽에서도 Team에 속한 멤버의 리스트를 조회할 수 있도록 할 수 있다.
다대일 양방향 매핑 - @OneToMany(mappedBy = "xxx")
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
아래와 같이 @OneToMany 어노테이션을 통해 반대로 Team 쪽에서 members 리스트를 필드로 가질 수 있다.
(mappedBy = "team") 속성은 꼭 추가해야 한다. Member의 "team" 필드에 의해 매핑되었다는 의미이다.
List<Member> memberList = teamA.getMembers();
위와 같이 Team 객체에서 getter 메서드로 members 리스트를 불러다 사용할 수 있다.
그러나 이것은 테이블과는 아무런 관련이 없는 기능이다.
이 members라는 필드는 자바 코드에서만 편의를 위해 사용하려고 만드는 부가적인 기능일 뿐이다.
Member member = new Member();
teamA.getMembers().add(member);
위와 같이 teamA에서 getMembers()로 teamA에 속한 memberList를 불러와서 그 list에 새로운 멤버를 추가해도
JPA 영속성 컨텍스트와 DB의 테이블에는 아무런 영향도 생기지 않는다.
즉, 이 members 필드는 오직 조회만을 위한 필드이다.
여전히 해당 팀에 속한 멤버를 추가하려면 멤버를 만들고, member.setTeam(teamA) 로 추가해야 한다.

위의 그림과 같이 객체의 연관관계에서는 Team.members의 OneToMany 연관관계가 추가되었지만,테이블 상에서는 아무런 변화가 없는 것을 알 수 있다. 그러나 또 다른 문제가 있다.JPA의 쓰기 지연 때문에 memberC.setTeam(teamA)를 실행해도 해당 트랜잭션이 커밋되기 전까지는
teamA.members에 memberC가 포함되지 않을 수 있다.
따라서 @OneToMany(mapped = "team")으로 멤버 리스트를 사용하려 할 때는 추가적으로 편의 메서드를 생성하여 이 부분까지 관리하는 것이 좋다.
편의 메서드 생성
@Entity
public class Member {
//생략
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
}
위와 같이 테이블과는 아무 관련이 없지만 team.getMembers(). add(this) 코드를 추가함으로써 예기치 못한 오류를 방지할 수 있다. 여기서 this는 Member 객체 자신을 나타낸다.
( 기본 setter인 setTeam과의 구분을 위해 메서드 명을 changeTeam으로 설정했다. )
결론
@ManyToOne은 가장 많이 사용하는 기본적인 매핑 방법이고, 가장 권장되는 방법이다.
@ManyToOne 매핑 시에 추가로 @OneToMany(mappedBy = "xxx")을 통해 양방향 매핑으로 설정할 수 있지만, 이 부분은 항상 사용하길 권장되진 않는다.
꼭 필요할 때만 사용하면 좋을 것 같다. (Team에 속한 멤버 리스트 데이터가 자주 필요한 경우 등)
@OneToMany(일대다 매핑)
Team과 Member 사이의 OneToMany 매핑도 사실 현실에서 생각해 보면 똑같은 관계이다.
하나(One)의 Team에 여러 명(Many)의 Member가 속해있는 것이다.
Table에서도 똑같다. 여러 Member는 TEAM_ID를 FK로 가지게 된다.
그러나 객체 세상에서만 조금 다르다.
Team의 members 필드에서 Member를 관리하고, Member 객체에서는 Team을 알 수 없다.
@Entity
public class Team {
@Id @GeneratedValue
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
}
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
}
이렇게 설계하면 Member 객체에서는 자신이 속한 Team의 정보를 알 수 없고, Team에서만 Member 리스트를 알 수 있다.
이 매핑에서 연관관계의 주인은 Team이 된다.
테이블에서 보자면 Team.getMembers(). addMember(memberA);를 실행했을 때,
Team 테이블에는 변화가 없고 MEMBER의 TEAM_ID를 UPDATE 하는 쿼리가 날아간다.

일대다 단방향 매핑의 단점
이 경우에는 Member객체에는 TEAM관련 속성이 없지만, MEMBER 테이블에는 TEAM_ID(FK)가 존재하게 된다.
즉, 엔티티가 관리하는 FK가 다른 테이블에 존재하게 된다.
또한 Team을 건드렸는데 Member에 대한 UPDATE SQL 나간다.
뭔가 자연스럽지 못하고 객체와 테이블의 불일치가 존재한다.
따라서 이 방법은 권장하지 않는 방법이다.
특별한 이유가 없다면 Member에 @ManyToOne으로 Team 필드를 갖게 하는 방식을 사용하는 게 좋을 것 같다.
그리고 Team에서 members가 필요하더라도 ManyToOne 양방향 매핑을 사용하는 게 더 나은 방법이다.
일대다 양방향 매핑 (공식적 x)
이 방법은 공식적으로 제공하는 방식이 아니다. 꼭 필요하지 않으면 사용하지 않는 것을 권장한다.
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "TEAM_ID", insertable = false, updatable = false) //읽기 전용으로 설정
private Team team;
}
Member 객체에 Team 필드를 추가하여 ManyToOne 연관관계의 주인처럼 만들고,
JoinColumn에서 쓰기 기능을 사용하지 못하게 만든다. ( insertable = false, updatable = false )
이렇게 하면 Member에서 Team을 조회하는 기능을 추가할 수 있다.

이 방법은 딱 봐도 이유 없이 복잡해지고 억지스러운 부분이 많다.
다대일 단방향을 이용하고, Team에서 members가 필요하다면 다대일 양방향 매핑을 이용하자!
(참고) 인프런 - 김영한 님 '자바 ORM 표준 JPA 프로그래밍 - 기본 편'
https://www.inflearn.com/course/ORM-JPA-Basic
'JPA & QueryDSL' 카테고리의 다른 글
[JPA] 연관관계 매핑 (1) | 2023.10.06 |
---|---|
[JPA] 연관관계 매핑 - @OneToOne 일대일 매핑 (0) | 2023.10.06 |
[JPA] @Entity - 필드와 컬럼 매핑의 여러가지 속성 (1) | 2023.10.04 |
[JPA] @Entity - 기본 키 매핑 (1) | 2023.10.04 |
[JPA] 엔티티 매핑 (0) | 2023.10.04 |