Jpa/series

[jpa 3편] OneToOne 정리

무대포 개발자 2022. 6. 16. 22:51
728x90
반응형

목차는 jpa series 목차 에 있습니다.

source 는 Github 에 있습니다.

[jpa 3편] OneToOne 정리

OneToOne JoinColumn, mappedBy 상세 설명

  • JoinColumn 은 조인 대상이 되는 정보를 명시하는 annotation 입니다.
    • JoinColumn(name = "외래키 이름", referenceColumn = "조인할 컬럼")
    • referenceColumn 이 설정 되지 않으면 기본 값으로 조인할 테이블의 PK 가 설정 됩니다.
    • 여기서는 Address 테이블의 address_id 로 설정 됩니다. (아래 예시 참조)
    • 여기서 주의할게 JoinColumn(name = "외래키 이름") 은 Foreign Key 의 이름입니다. 실제 조인할 컬럼은 referenceColumn 입니다.
  • JoinColumn 을 가진 Member 테이블이 연관관계의 주인이 되며, address_id 는 Member 테이블에 생깁니다.
  • mappedBy 는 연관관계 주인이 아닌 테이블에서 연관관계 주인 테이블과 맵핑하기 위해 사용합니다.

public class Member {

    @OneToOne
    @JoinColumn(name = "address_id")
    private Address address;
}


public class Address {

    @Id @Column(name = "address_id")
    private Long id;

    @OneToOne(mappedBy = "address")
    private Member member; 

}

insertable, updateable, cascade 정리

insertable
엔티티 저장시, 해당 필드도 같이 저장합니다. (default true)
false로 설정하면, insertable = false 로 설정한 필드는 저장할 때, DB에 저장하지 않습니다.
즉, false 옵션은 읽기 전용일 때, 사용합니다.
updatable
엔티티 수정시, 해당 필드도 같이 수정합니다. (default true)
false로 설정하면, updateable = false 로 설정한 필드는 수정할 때, DB 필드를 수정하지 않습니다.
즉, false 옵션은 읽기 전용일 때, 사용합니다.
cascade
cascade 는 영속성 전이를 의미합니다. 
부모 엔티티가 영속화될 때 자식 엔티티도 같이 영속화 되거나, 부모 엔티티가 삭제될 때, 자식 엔티티도 삭제되는 등 영속성 전이 기능을 구현할 수 있습니다.

CascadeType.ALL: 모든 Cascade를 적용합니다.
CascadeType.PERSIST: 엔티티를 영속화할 때, 연관 엔티티도 함께 persist 합니다.
CascadeType.MERGE: 엔티티 상태를 Merge 할 때, 연관된 엔티티도 모두 병합합니다.
CascadeType.REMOVE: 엔티티를 제거할 때, 연관된 엔티티도 모두 제거합니다.
CascadeType.DETACH: 부모 엔티티를 detach() 하면, 연관 엔티티도 detach() 상태가 됩니다.
CascadeType.REFRESH: 상위 엔티티를 refresh 할 때, 연관된 엔티티도 새로고침 합니다.

OneToOne 특징 (lazy loading)

  • 테이블을 조회할 때, 외래 키를 갖고 있는 연관관계 주인 entity 에서는 지연로딩이 동작하지만, mappedBy 로 연결된 테이블은 지연로딩이 동작하지 않습니다.
    • 연관관계 주인이 아닌 entity 에서는 N + 1 쿼리가 발생합니다.
  • 왜 이런 현상이 발생하는지 예시는 다음과 같습니다.
    • Member 와 Address 는 1:1 관계라고 가정하겠습니다.
    • 아래 예시를 보면 연관관계 주인은 Member 이기에 FK 를 Member 테이블에서 관리합니다. Member 테이블에 address_id 컬럼이 존재합니다.
    • Member 테이블을 조회할 때는 쿼리가 1번만 발생하지만 Address 테이블을 조회할 때는 쿼리가 n+1 만큼 실행이 됩니다.
    • 왜냐하면 Address 테이블에는 Member 에 대한 정보가 없기에 Member 테이블을 조회해야지만 Member 정보를 가져올 수 있기 때문입니다.

public class Member {

    @OneToOne
    @JoinColumn(name = "address_id")
    private Address address;
}


public class Address {

    @Id @Column(name = "address_id")
    private Long id;

    @OneToOne(mappedBy = "address")
    private Member member; 

}

OneToOne Example Source

  • entity
@Entity
@Getter
@NoArgsConstructor
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "member_id")
    @Setter
    private Long id;

    @Column
    private String name;

    @Column
    private String telNo;

    @Column
    private int age;

    @OneToOne
    @JoinColumn(name = "address_id")
    private Address address;

    public void setAddress(Address address) {
        this.address = address;
        this.address.setMember(this);
    }
  • test source

    @Test
    public void MEMBER_ONE_TO_ONE_테스트() throws Exception {
        Member member = Member.builder()
                .age(10)
                .name("test")
                .telNo("123")
                .build();

        Address address = Address.builder()
                .addr("주소")
                .build();

        // 양방향 연결
        member.setAddress(address);

        memberRepository.save(member);

        addressRepository.save(address);

        // when
        List<Member> list = memberRepository.findAll();
        Address addr = list.get(0).getAddress();

        // then
        Assert.assertThat(addr.getAddr(), is("주소"));
    }

Reference