양방향 관계에서 편의 메서드를 사용해야하는 이유 💦

코린이서현이·2024년 7월 19일
1

스프링

목록 보기
1/4
💡 편의메서드인데 나는 편의하지 않다묜?!
Example Image *너.무.우.ㅅ.겨요*

현재 상황

양방향 매핑에서 Lazy를 걸어놓은 객체와 잘 연동이 되는지 테스트하고 싶었다.

하지만 편의메서드를 작성하지 않아 영속성 컨텍스트를 비워두고 (EntityManeger.clear사용 )해서 테스트 해야하는 번거로움이 있었다. 멘토님께서 퍈의메서드를 알려주셔 요부분 알아보려고 한당

편의메서드에 대해서 간략히 알아보자!

양방향 관계에서 주인이 아닌 엔티티의 칼럼이 Lazy 설정이 되어있는 상황에서, 주인객체를 변경하거나 작성했을때 주인이 아닌 엔티티의 칼럼이 자동 반영되지않는다.

순수자바 객체에서도 상태를 유지하기 위해서 편의메서드를 사용한다.

( 네? 💦 )

말이 어려운 것 너무 인정… !! 편의 메서드는 JPA가 일하기 전!! 순수 객체 상태에서도 연관관계의 변경을반영하기 위해 사용한다.

이 상황을 설명하기 전에 양방향 관계와 , 관계의 주인, Lazy를 이해해야한다.

양방향 관계가 무엇인가요?

양방향 관계는 연관관계에 잇는 객체가 서로를 필드로 가지고 있어 A에서 B로 , B에서 A로 접근이 가능하게 두는 객체 연관관계다.

✌️객체✌️라는 표현을 두는 이유 생각해보면서 읽으면 더 이해가 잘 될 것 같다.

양방향 관계를 다뤄본 분들은 @mappeBy속성을 봤을 것이다.

그리고 “@mappeBy을 가지지 않은 엔티티가 “연관관계의 주인”이다”라는 말도 들어봤을 것이다.

양방향 관계는 데이터베이스에는 없어요. 있었는데? 아뇨 없어요.

DB를 공부했던 기억을 다시 잘 꺼내보자..

데이터베이스 테이블에서 외래키를 서로 주고 받는가? 아니다!!

실제 DB의 테이블은 외래키 하나로만 두 테이블의 연관관계를 관리한다. 하지만 객체지향자바는 상품 → 위시리스트 , 위시리스트 → 상품을 관리하길 원한다.

그러나 실제 테이블에는 이런 방식으로 서로의 외래키를 가질 수 없기 떄문에, 둘 중 하나만 외래키를 가져야한다.

이때 자바에서 외래키를 가지지 않고, 관리되는 대상이 @mappeBy 속성을 가진다.

즉 외래키의 주인은 이 속성을 가지지 않는다. 외래키를 가지는 객체가 연관관계의 주인인 것이다.

연관관계 주인은 노예의 노예?

앞에서 설명을 했지만 다시 짚어보자.

연관관계의 주인은 데이터 베이스에서 연관관계, 즉 외래키의 주인, 외래키를 가진다는 뜻이다.

연관관계의 주인은 외래키를 가진다.

그렇다면 주인이 아닌 객체는 어떤 모습일까?

답은 간단하다 주인이 아닌 객체의 테이블은 그저 깔끔- 하다.

연관관계에 있는 테이블의 정보를 전-혀 가지고 있지 않다.

따라서 주인이 아닌 객체가 해당 필드를 가져오기 위해서는 해당 외래키를 가진 테이블에 조회 쿼리를 날려 DB의 값을 가져와야한다.

양방향 관계에서 데이터를 저장하는 방법

상품과 위시리스트는 일대다 양방향 연관관계로 이루어져있다.

상품 코드의 일부

<@Entity
public class Product {

    @OneToMany(mappedBy = "product", fetch = FetchType.LAZY, cascade = CascadeType.DETACH)
    private List<WishProduct> wishProducts;
} 

위시프로덕트 코드의 일부

@Entity
@Table(name = "wish-products")
public class WishProduct {

    @ManyToOne
    @JoinColumn(name = "product_id", nullable = true)
    public Product product;
} 

다음 중 객체를 저장하는 올바른 방법은?

🧑‍💻 product.getWishProducts에 add(위시리스트) 한다음 Product를 save

product.getWishProducts().add(New WishList);
productRepository.save(product);

🧑‍💻 wishlist에 this.product = (프로덕트) 한 다음 wishlist를 save

WishProduct wishProduct = new WishProduct(~~, product);
wishProductRepository.save(wishProduct);    

직감적으로도 두 번째가 왠지~ 맞을 것 같지만 직감이 아닌 실제 이유가 있다.

다른 테이블을 내 테이블과 연관시키는 방법은 외래키, 연관관계 주인만이 외래키를 가지기 때문에 값을 변경할 수 있는 것은 WishProduct뿐이다.

친절한 JPA가 알아서 값을 저장할 것도 같지만 아니다. 💦

양방향 연관관계에서는 관계의 주인이 값을 저장할 수 있다.

순수한 자바 상태도 고려해주세요!

해당 값이 저장되려면 JPA가 동작해서 저장할때까지 기다려야한다. 따라서 JPA가 동작하기 전 순수한 자바 상태에서는 주인이 아닌 객체의 필드가 정상 반영되지 않는다.

물론 JPA가 실행하니까 값은 정상 반영되지만! 우리는 객체 지향 프레임워크 스프링을 사용하구 있다구~!~!

객체 관점에서 자연스러운 것은 저장시 두 객체 모두 값이 반영되는 것이다.

이때 사용하는 것이 주인 객체의 로직 내부에서 주인이 아닌 객체의 값도 변경되도록 유지하는 편의 메서드이다.

편의메서드 드디어 등장;;

편의 메서드는 주인 객체의 로직 내부에서 주인이 아닌 객체의 값도 변경되도록 유지하는 메서드

예시를 보자! 편의 메서드 추가 전 WishProduct 로직

    public WishProduct(Product product, User user) {
        this.product = product;
        this.user = user;
    }

편의 메서드 추가 후 WishProduct 로직

public WishProduct(Product product, User user) {
        this.product = product;
        this.user = user;
        product.getWishProducts().add(this);
} 

이렇게 되면 JPA가 동작하기 전에도 Product의 값이 반영된다.

편의 메서드는 주인 객체의 로직 내부에 두자!

왜일까? 이 글을 읽고 곰곰히 생각해봤다면 이유을 알 수 있을 것이다. 바로 양방향관계에서 값을 저장하는 것은 주인 객체로 둬야하고 서비스단이나 외부로직에 편의 메서드를 두는 것은 수고롭고, 놓칠 위험이 존재한다.

예시를 두고 가겠다.

편의 메서드의 수정

    public void updateWishProduct(새로운 위시프로덕트) {
        //변경전 
        product.getWishProducts().remove(this);
        변경로직 수행 (this.name = name 등)
        product.getWishProducts().add(this);
    }

편의 메서드의 삭제

    public void remove() {
        product.getWishProducts().remove(this);
    }

개인적으로 느낀 점

객체지향프레임워크 스프링을 공부하다보면 객체지향의 장점장점장점장점…을 접하게 된다. 
하지만 여러 개발관점마다의 장점이 있다.

편의메서드를 사용해야하는 이유에서 관계형 데이터 베이스의 장점을 느낄 수 있었다. 
외래키하나로 관리하니까!! 

여러 관점을 사랑하는 개발자가 된다면 최고 개발자가 되는 건 한 순간일지도?!

마무리-!

😠 편의 메서드 귀찮고! 고민하기 싫어!
라고 생각하지 말자. 순수한 자바 상태에서도 상태를 유지할 수 있도록 해야한다.

profile
24년도까지 프로젝트 두개를 마치고 25년에는 개발 팀장을 할 수 있는 실력이 되자!

0개의 댓글