JPA

5장 연관관계 매핑 기초

iksadnorth 2023. 8. 30. 21:50

해당 게시물은 책 '자바 ORM 표준 프로그래밍'을 읽고 작성했습니다.

👣 개요

OOP와 SQL 사이의 가장 큰 간극은 '객체의 참조'와 'table 사이의 관계'일 것이다.

OOP의 경우, 어떤 한 객체 A가 다른 객체 B를 필드로서 참조하고 있다고 해도
객체 B가 객체 A를 필드로 가지는 것은 아니다.
하지만 SQL의 경우, 어떤 한 테이블 C가 다른 테이블 D를 참조하고 있다면
테이블 D는 테이블 C를 Join 연산으로 참조할 수 있다.

만약 위 상황을 해결하기 위해 객체 A와 객체 B가 서로를 필드로 참조한다 하더라도
또다른 고려사항이 생긴다.

OOP의 경우, A와 B가 참조하면 둘 사이는 완전 대칭을 이룬다.
하지만 SQL의 경우, C 혹은 D 둘 중 하나는 FK[외래 키]를 가지고 있어서 완전한 대칭을 이루지는 못한다.

이렇게 OOP와 SQL 사이에는 '참조 방향'과 '참조 키 주인'라는 2가지 이질적 특성을 
고려해서 다뤄야 한다는 결론이 나온다.

 

👣 단방향 연관관계

위에서는 마치 JPA를 이용하기 위해선 무조건 객체 A와 객체 B가 서로를 참조해야 할 것 마냥
서술했지만 사실 필요하지 않다면 굳이 그럴 필요는 없다.
그러한 경우에 단방향 연관관계를 맺어준다.

public class Member {
    private Long id;
    private String username;
    private Team team; // 단방향 참조.
}

public class Team {
    private Long id;
    private String name;
}
create table MEMBER (
    ID bigint primary key,
    USERNAME varchar(100),
    TEAM_ID bigint 
);

create table TEAM (
    ID bigint primary key,
    NAME varchar(100)
);

alter table MEMBER 
add constraint fk_member_team 
foreign key TEAM_ID references TEAM (ID); -- 외래 키를 가지고 있다.

위 코드를 보면 OOP에서는 단방향으로만 참조를 하고 있다.
Member 객체는 Team 객체를 필드로 가지고 있어서 Member.getTeam()이 가능하다.
하지만 Team.getMember()는 불가능하다.

하지만 SQL에서는 'Select * From Member Join Team ...'도 가능하고
'Select * From Team Join Member ...'도 가능하다.

 

👣 연관관계 매핑 어노테이션

@JoinColumn
엔티티에 외래 키를 매핑하기 위해 사용함.
해당 어노테이션이 부여된 필드는 FK로서 사용됨.

  • name
    매핑할 외래 키 칼럼 이름.
    기본값은 ["%s_%s" <- '외래 키 필드명', '참조할 테이블 PK 칼럼명']이다.
    예를 들어, 위 경우엔, "team_id"가 된다.
  • referencedColumnName
    외래 키가 참조할 테이블의 칼럼명.
    기본값은 참조할 테이블의 PK 칼럼명이다.
  • foreignKey
    외래 키 제약조건을 직접 지정할 때, 사용.

@ManyToOne
참조 관계가 '다대일'인 경우에 사용함.
예를 들어, 게시판 회원을 A라고 하고 게시판의 글을 B라고 한다면
A는 여러개의 B를 소유할 수 있지만 B는 하나의 A만 소유할 수 있다.
이경우 'A : B = 다 : 일'라고 여길 수 있다.
해당 Annotation은 해당 엔티티가 A인 경우, 참조 키 필드에 적용하면 된다.

  • optional
    연관된 엔티티가 항상 존재할 필요가 없다면 true로 설정하고
    아니라면 false값을 부여한다. 기본값은 true다.
  • fetch
    DB에서 Java로 가져올 때, 연관된 객체를 Join을 이용해 함께 가져올지
    아니면 Join을 사용하지 않고 따로 가져올지에 대한 속성값.
    FetchType.EAGER : Join을 항상 적용한다.
    FetchType.LAZY : Join을 적용하지 않는다.
    @xxxToOne의 기본값은 FetchType.EAGER로 설정되어 있으며
    @xxxToMany의 기본값은 FetchType.LAZY로 설정되어 있다.
  • cascade
    영속성 전이 기능 전략에 대한 옵션이다.
    연관된 속성을 함께 삭제 혹은 생성을 할 수 있게 도와주는 옵션이다.
    이 속성은 서비스에 심각한 악영향 혹은 엄청난 이점을 가져다 줄 수 있기 때문에
    기민하게 적용해야 한다.

결국, [단방향 연관관계]에서 제시한 엔티티는 다음과 같이 매핑할 수 있다.

@Entity @Table("member")
public class Member {
    @Id
    private Long id;
    private String username;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "team_id")
    private Team team; // 단방향 참조.
}

@Entity @Table("team")
public class Team {
    @Id
    private Long id;
    private String name;
}

 

👣 양방향 연관관계

만약 A와 B가 서로를 참조해야 한다면 양방향 연관관계를 맺어주면 된다.

public class Member {
    private Long id;
    private String username;
    private Team team; // Member -> Team 참조.
}

public class Team {
    private Long id;
    private String name;
    private List<Member> members; // Team -> Member 참조.
}
create table MEMBER (
    ID bigint primary key,
    USERNAME varchar(100),
    TEAM_ID bigint 
);

create table TEAM (
    ID bigint primary key,
    NAME varchar(100)
);

alter table MEMBER 
add constraint fk_member_team 
foreign key TEAM_ID references TEAM (ID); -- 외래 키를 가지고 있다.

위 경우는 명백히 OOP에서도 SQL에서도 양방향 참조가 가능하다.

위의 경우 다대일 관계여서 한 쪽이 List 타입인거지 일대일 관계에서는 둘다 List를 사용하지 않아도 된다.

하지만 이 경우, 유심히 살펴야 하는 부분은 SQL에서 외래 키는 오직 Member만 가지고 있다는 점이다.
이 부분으로 인해 마냥 대칭적인 관계를 유지할 수 없다.
때문에 이러한 부분을 OOP에서도 반영을 해줘야 진정한 의미의 JPA를 구현할 수 있다.
이 때, Member가 외래 키를 소유하고 있으므로 연관관계의 주인은 Member라고 말할 수 있다.
연관관계의 주인[Member]이 Team을 추가해야지 연관관계가 생성이 되는 것이지
연관관계의 주인이 아닌 엔티티[Team]는 Member와 관계를 맺고 싶어해도 맺어질 수 없을 것이다.

 

👣 연관관계 매핑 어노테이션

@OneToMany
참조 관계가 '일대대'인 경우에 사용함.
해당 Annotation은 엔티티 클래스가 '일'인 경우에 적용함.
보통 List타입에 붙는다고 생각하면 편함.

  • targerEntity
    상대 엔티티의 클래스를 전달하면 됨.
    기본값은 Collection의 제네릭 타입이 설정됨. [Ex. 필드 타입이 List<User>라면 User로 설정됨.]
  • mappedBy
    참조할 엔티티 클래스의 참조 키의 필드명.
    해당 속성으로 명시된 칼럼이 참조 키의 주인으로 설정됨.
  • orphanRemoval
    주인 엔티티가 할당되지 않으면 자동으로 삭제할지의 여부.

결국, [양방향 연관관계]에서 제시한 엔티티는 다음과 같이 매핑할 수 있다.

@Entity @Table("member")
public class Member {
    @Id
    private Long id;
    private String username;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "team_id")
    private Team team; // Member -> Team 참조.
}

@Entity @Table("team")
public class Team {
    @Id
    private Long id;
    private String name;
    
    @OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
    private List<Member> members; // Team -> Member 참조.
}

 

'JPA' 카테고리의 다른 글

7장 고급 매핑  (0) 2023.08.31
6장 다양한 연관관계 매핑  (0) 2023.08.31
4장 엔티티 매핑  (0) 2023.08.30
3장 영속성 관리  (0) 2023.08.27
2장 JPA 시작  (0) 2023.08.27