다양한 연관관계 매핑

twocowsong·2023년 4월 28일
0

김영한_jpa

목록 보기
7/13

연관관계 매핑시 고려사항 3가지

  • 다중성
    - [N:1, 1:N, 1:1, N:M]
  • 단방향, 양방향
    - 테이블 : 외래키 하나로 양쪽 조인이 가능, 사실 방향이라는 개념이 없음
    • 객체 : 참조용 필드가 있는 쪽으로만 참조가 가능
  • 연관관계의 주인
    • 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺음
    • 객체 양방향 관계는 A->B, B->A 처럼 참조가 2군데
    • 객체 양방향 관계는 참조가 2군데 있음. 둘중 테이블의 외래 키 를 관리할 곳을 지정해야함
    • ex) Member.team , Team.member 가 양방향 연관관계일 경우 어디를 수정해야지 바뀔지를 정해야함

다대일 [N : 1]

다대일 단방향

위와 같이 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 로 연관관계의 주인을 설정하면, 양방향연관관계의 설정이 완료됩니다.

일대다 [1:N]

일대다 단방향

  • 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인
  • 테이블 일대다 관계는 항상 다(N) 쪽에 외래 키가 있음
  • 객체와 테이블의 차이 때문에 반대편 테이블의 외래 키를 관리하 는 특이한 구조
  • @JoinColumn을 꼭 사용해야 함. 그렇지 않으면 조인 테이블 방식을 사용함(중간에 테이블을 하나 추가함)
@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 실행
• 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자

일대일 [1:1]

일대일 관계


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로 만들수있습니다.

profile
생각하는 개발자

0개의 댓글