Hibernate/JPA(EntityManager)

[JPA] @OneToOne 단방향/양방향 - 외래키 정책 기준

유혁스쿨 2023. 6. 29. 12:23
728x90
반응형

@OneToOne 단방향

1:1 관계는 그 반대도 1:1이 된다.

따라서 주 테이블(Master)이나 대상 테이블(Detail) 중에 외래키를 어느쪽을 선택하든지 지정할 수 있다.

(둘 중 한곳에만 넣어주면 된다.)

예를들어 User와 Team이 있다면, User에 외래키를 넣을 수도 있고, Team에 외래키를 넣을 수도 있다.

 

데이터베이스 입장에서는 외래키에 Unique 제약조건이 추가된것이 1:1관계가 된다.

회원이 락커를 하나만 사용할 수 있고, 락커 입장에서도 한명의 회원에 의해서만 사용되어지는 상황이 있다.

이때 회원에 락커의 PK를 FK(Unique)를 설정할 수 있거나, 락커에 회원의 PK를 FK(Unique)로 설정할 수 있다.

마치 다대일 단방향 연관관계 매핑과 유사하다.

@Entity
@Getter @Setter
public class Locker {
    @Id @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;
    private String lockername;
}
@Entity(name = "USERS")
@Setter
@Getter
@ToString
public class User {
    @Id @GeneratedValue
    @Column(name = "USER_ID")
    private Long id;
    private String username;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
}

User에 lokcer_id FK컬럼이 추가된다.

 

@OneToOne 양방향

ManyToOne에서의 양방향 연관관계와 동일하게 Lokcer 엔티티 클래스에 아래와 같이 설정해 주면 된다.

@OneToOne(mappedBy = "locker")
private User user;
@Entity
@Getter @Setter
public class Locker {
    @Id @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;
    private String lockername;
    
    @OneToOne(mappedBy = "locker")
    private User user;
}

이렇게 되면 Locker에서 user필드를 읽기 전용으로 사용할 수 있게 된다.

 

위의 예제가 가장 심플하게 떨어지는 예제로, 다대일 양방향 매핑과 같이 외래키가 존재하는 곳이 연관관계의 주인이 되며, 반대편 클래스에 mappedBy를 사용하여 객체 타입의 필드를 선언하면 된다.

 


대상 테이블(Detail)에 외래키 관리

1:M 관계에서 연관관계의 주인인 1에서 대상 테이블 M의 FK를 관리를 할수 있었다.

하지만 1:1 관계에서는 대상 테이블 에서 외래키 관리가 불가능하다.

예를들어 User에 FK가 존재하고 Locker가 연관관계의 주인인 상황일때, Locker에서 User의 FK를 관리하는 상황은 지원하지 않는다.

1:1관계에서는 나의 엔터티에 존재하는 외래키를 내가 직접 관리해야 한다고 이해하면 쉽다.


1. locker_id(FK)를 User에 추가

2. user_id(FK)를 Locker에 추가

 

DATABASE 설계상 둘 중 어떠한 상황을 선택해도 사실 1:1 관계가 유효하게 성립된다. (정답은 없다)

 

DBA관점에서 봤을 때, 테이블이 한번 만들어지면 변경하기 어렵다.

미래에 한명의 회원이 여러개의 락커를 사용할 수 있는 상황이 발생할 수 있다.

이렇게 되면 비즈니스 룰이 바뀐다.

2. user_id(FK)를 Locker에 추가하는 상황에서 FK에 Unique제약조건만 뺀다면 1:1에서 1(user):M(locker)로 쉽게 변경이 가능해진다.

 

만약 동일한 변경 상황을 전제조건으로 두고, 1. locker_id(FK)를 User에 추가된 상태라면 Locker에 컬럼을 추가하고 기능을 변경해야할 상황이 발생할 수 있다. (변경 포인트가 많아지게 된다.)

또한 JPA에서는 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩이 된다.

(지연 로딩으로 설정했을 때, 연관된 엔터티가 있으면 프록시 객체가 대신 들어가면 되지만, 엔터티가 없으면 null이 들어가야 한다.)

User 테이블에 FK가 존재하지 않으면 Locker도 함께 조회해야 하는 현상이 발생하는데, Lazy로딩은 각각 따로 검색하기 때문에 즉시로딩을 통해서 JOIN으로 불러와서 프록시 객체를 채워줘야 하기 때문이다.

 

하지만, 하나의 Locker를 여러명의 회원이 사용할 수 있게끔, 비즈니스 룰이 바뀌게 된다면 

1.번의 선택이 맞게 되는거다.

 

개발자 관점에서는 User에 locker_id(FK)를 갖고 있는게 성능등 여러가지 장점이 있다.

예를들어 User가 Locker를 가지고 있는지 여부에 대해 (User테이블을 SELECT를 많이한다는 가정 하에) 성능상으로 따지자면

비즈니스 로직에서 locker 값의 존재 유무에 대해 조건문을 생성하는 코드를 만들고, User가 Locker값을 갖고있는지에 대해 쉽게 조회할 수 있게 된다. (단점은 값이 없다면 외래키에 null을 허용해야한다.)

 

개발자 관점에서 먼 미래를 생각하지 않았을때의 이상적인 설계를 한다면

명확하게 1:1관계라면 User에서 Locker를 갖고 있도록 하는게 개발자 입장에서는 유리하지만 DBA입장에서는 TradeOf 가 발생할 수 있다. (입장차이, 관점차이)

728x90
반응형