위와 같이 Member, Team이 있을경우 Member(N) : Team(1)일때, N에 FK가 있어야 테이블 설계가 정상적으로 이루어집니다.
...
@ManyToOne // 회원은 N이고 팀은 1이기 때문
@JoinColumn(name = "TEAM_ID") // 연결할 컬럼명
private Team team;
...
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
mappedBy 로 연관관계의 주인을 설정하면, 양방향연관관계의 설정이 완료됩니다.
@Getter
@Setter
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
public Member() {
}
}
@Getter
@Setter
@Entity
public class Team {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private List<Member> members = new ArrayList<>();
}
members 의 값처럼 설정을 해야합니다.
Member member = new Member();
member.setUsername("member1");
em.persist(member);
Team team = new Team();
team.setName("teamA");
team.getMembers().add(member);
em.persist(team);
tx.commit();
저장 로직이 위와 같을 경우 아래처럼 SQL이 실행됩니다.
Hibernate:
call next value for hibernate_sequence
Hibernate:
call next value for hibernate_sequence
Hibernate:
/* insert hello.Member
*/ insert
into
Member
(USERNAME, MEMBER_ID)
values
(?, ?)
Hibernate:
/* insert hello.Team
*/ insert
into
Team
(name, TEAM_ID)
values
(?, ?)
Hibernate:
/* create one-to-many row hello.Team.members */ update
Member
set
TEAM_ID=?
where
MEMBER_ID=?
update되는 형식으로 Member의 값이 변경되게됩니다.
• 엔티티가 관리하는 외래 키가 다른 테이블에 있음(Member 테이블에 TEAM_ID FK가 존재하는데, 입력은 team.getMembers().add(member); 팀객체에서 ADD가 되고있음)
• 연관관계 관리를 위해 추가로 UPDATE SQL 실행
• 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자
Locker 테이블이 추가될경우, Member테이블에 LOCKER_ID가 추가될수있습니다.
아래에 일대일 양방향연관관계 예제입니다.
@Entity
@Getter
@Setter
public class Locker {
@Id@GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
@Getter
@Setter
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
public Member() {
}
}
연관관계의 주인을 mappedBy를통해 설정하는 방법은 똑같습니다.
다대일(@ManyToOne) 단방향 매핑과 유사합니다.
그와 반대인 Member테이블에 FK가 있는것이 아닌, Locker테이블에 FK가 있는 경우가 있습니다.
허나 위 상황의 일대일 단방향은 JPA에서 지원하지 않습니다. 단 양방향관계로 개발하시면됩니다.
관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할수 없습니다.
연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야합니다.
Member와 Product사이에 Member_product같은 중간 테이블이 필요합니다.
단, 객체는 컬렉션을 사용해서 객체2개로 다대다 관계가 가능합니다.
@Getter
@Setter
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@ManyToMany
@JoinTable(name = "MEMBER_PRODUCT")
private List<Product> products = new ArrayList<>();
public Member() {
}
}
@Setter
@Getter
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToMany(mappedBy = "products")
private List<Member> members = new ArrayList<>();
}
소스코드처럼 다대다 양방향으로 설정시 MEMBER_PRODUCT테이블이 생성되는걸 확인 할 수 있습니다.
하지만, 이렇게 자동으로 만들어주게되면 개발자가 생각하지못한 쿼리등 문제가 발생할수있습니다.
이를 해결하기위해, @ManyToMany -> @OneToMany, @ManyToOne로 바꾸고 MEMBER_PRODUCT테이블을 엔티티로 승격하면 됩니다.
위 그림처럼 자바소스코드는 아래와같이 구현이 가능합니다.
@Getter
@Setter
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "MEMBER_ID")
private Long id;
@Column(name = "USERNAME")
private String username;
@OneToMany(mappedBy = "members")
private List<MemberProduct> memberProduct = new ArrayList<>();
public Member() {
}
}
@Getter
@Setter
@Entity
public class MemberProduct {
@Id @GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "MEMBER_ID")
private Member members;
@ManyToOne
@JoinColumn(name = "PRODUCT_ID")
private Product products;
}
@Setter
@Getter
@Entity
public class Product {
@Id @GeneratedValue
@Column(name = "PRODUCT_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "products")
private List<MemberProduct> memberProduct = new ArrayList<>();
}
MemberProduct엔티티를 추가하여 Member와 Product를 @OneToMany로 만들수있습니다.