본문 바로가기
가치관 쌓기/개발 돌아보기

직접 코딩으로 느껴본 Spring Data JPA와 Spring Data JDBC 의 차이점

by simplify-len 2021. 7. 18.

기존의 트랜잭션 스크립트 패턴으로 구현된 코드를 JPA로 변경해보면서 의존성을 분리하고, 다시한번 트랜잭션 스크립트 패턴으로 구현된 코드를 Spring Data JDBC (Spring JDBC와는 다른 Spring Data 에서 제공하는 라이브러리입니다.) 로 변경하면서 느꼈던 차이점에 대해서 작성할 예정입니다.

Github - https://github.com/LenKIM/jwp-refactoring

트랜잭션 스크립트로 작성된 코드의 브랜치 - step1

JPA로 구현된 코드의 브랜치 - step3

Spring Data JDBC 로 구현된 코드의 브랜치 - try-spring-jdbc-step2-service


Index

  1. Referance 에서 말하는 Spring Data JDBC와, JPA의 차이점
  2. 내가 느낀 Spring Data JDBC와 JPA의 차이점
    • 연관관계를 바라보는 관점
    • 객체 하나를 바라보는 관점&도메인 주도 설계

1. Reference 에서 말하는 Spring Data JDBC와, JPA의 차이점

Reference 에서 말하는 JPA와 차이점은 JPA가 누리는 매직이 없다고 말합니다.

  • Entity 를 Load 할 때, 이 Entity 를 Lazy 로 하겠는가? Eager 로 하겠는가? 설정하는 부분
  • JPA의 DirtyCheck 가 JDBC 에서는 없습니다.
  • JDBC는 JPA에 비해 적은 Annotation 을 제공합니다.
  • Spring Data JPA 에 비해 JDBC는 오직 OneToOne, OneToMany, ManyToMany 의 연관관계만 가집니다.

2. 내가 느낀 Spring Data JDBC와 JPA의 차이점

연관관계를 바라보는 관점이 다르다.

Spring Data JDBC는 오직 제공되는 맵핑관계가 OneToMany, OneToOne, ManyToMany 만을 제공하며, 오직 단반향만으로 접근이 허용하도록 합니다.

그에 반해 Spring JPA는 더많은 부분을 제공하는데, ManyToOne, 양방향 을 제공합니다.

저는 여기서 양방향, 단방향 에 대해서 이야기해보려 합니다. JPA 에서는 @OneToMany, @ManyToOne 등의 맵핑 관계를 설정할 때, 양방향을 지원합니다. 이 때 사용되는 AnnotationsMappedBy,@JoinedColumn 등이 있습니다.

JPA의 양방향 관계에 대해서도 개발하면서 이해되지 않은 부분이 있어 내용을 남긴적 있는데요. JPA 에서 양방향을 제공하는 이유에 대해서 생각해보면 단방향 설정 후 데이터를 업데이트 할 경우에 불필요한 쿼리를 줄이기 위해서라고 생각합니다.참고자료 그러므로 양방향 관계가 나쁜 것은 아닙니다. 그러나, 저는 추후 유지보수 관점에서 생각해보았는데요.

양방향 설정은 당장은 쿼리의 숫자를 줄여, 성능상의 이점을 가질 수 있습니다. 그러나, 개발자의 실수, 설계된 아키텍쳐에 따라 엔티티 간의 관계 설정시 잘못될 수 있는 잠재적인 오류를 가집니다.

JPA로 맵핑 관계 설정시 사용했던 관계는 아래와 같았습니다.

image-20210718195949912

여기서 TableGroup 과 OrderTable 엔티티간의 관계만 살펴보면, 당시 개발할 때를 돌이켜 생각해보니 제가 잘못된 설계로 이어질 수 있었습니다. 위 Entity 관계 그래프 없이 양방향 설정 후, 데이터를 임의대로 Cascade하게 저장하고- 업데이트 하면서 무의식중에 아래와 같은 코드를 만들 수 있습니다.(일부 과장된 코드이지만, 그만큼 JPA 를 잘 사용하는 것은 어렵다는 것을 말씀드리고 싶었습니다.)

@Entity
public class OrderTable {

        ...

    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "table_group_id", foreignKey = @ForeignKey(name = "fk_order_table_table_group"))
    private TableGroup tableGroup;
    ...
}
@Entity
public class TableGroup implements Serializable {

    ...    

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "tableGroup", cascade = CascadeType.ALL)
    private List<OrderTable> orderTables = new ArrayList<>();

        ...
}

각각 Cascade 을 가지고, 어떤 엔티티가 어떤 사이프사이클을 가질지 코드로서 표현될 수 없었습니다.

그러나, Spring Data JDBC 의 경우에는 단반향만 제공합니다. 이 말이 의미하는 바가 무엇인지 고민하는데, 꽤 오랜시간이 걸린것 같습니다.

public class OrderTable {

    @Id
    private Long id;
    private int numberOfGuests;
    private boolean empty;

    @PersistenceConstructor
    public OrderTable(Long id, int numberOfGuests, boolean empty) {
        this.id = id;
        this.numberOfGuests = numberOfGuests;
        this.empty = empty;
    }
public class TableGroup {
    @Id
    private Long id;

    @Column("TABLE_GROUP_ID")
    private Set<OrderTableRef> orderTables;

        ...
}

JPA에서 발견할 수 있었던 외래키 맵핑이 없습니다. 이게 Spring Data JDBC의 재미있는 부분이라고 생각합니다.

만약에, OrderTable 의 특정 값을 변경하고 싶다면 어떻게 해야될까? JPA 라면 외래키를 사용해 변경을 시도했을 것이다. 그러나, JDBC에서는 이같은 설정이 없기 때문에 TableGroup 내에서 모든 OrderTables 를 찾은 다음에 변경을 시도해야 합니다.

위 내용만 들으면 마치 안좋은 점 처럼 들리지만, 만약에, OrderTable 의 특정 값을 변경하고 싶다면 어떻게 해야될까? 라는 질문자체는 사실 설계와 관련된 문제입니다.

이는 JDBC의 문제가 아니라 엔티티를 설계한 관점에서 다시 살펴봐야 합니다.

반대로, 이렇게 외래키를 조작할 수 없음으로써 얻을 수 있는 장점은 무엇일까요?

깨끗하게 모듈화된 도메인을 얻을 수 있다. 깨끗하게 모듈화된 도메인은 추후 데이터를 변경할 때 덩어리로 생각하기 때문에 유지보수하는 관점에서 성능을 포기하고 이점을 가져갈 수 있게 됩니다.

저와 같이 주니어 개발자는 처음보는 프로젝트에 JPA 프레임워크가 적용되어 있다면, 큰 그림을 그리는데 어려움을 겪습니다. 그러나 JDBC 에서는 코드가 강제로 나에게 이것만 신경써줘 라고 강제하게 될 수 있다고 생각했습니다.


객체 하나를 바라보는 관점&도메인 주도 설계

Spring Data JDBC 의 레퍼런스를 보면 바로 앞에 이런 내용이 있습니다.

image-20210718203340625

Spring Data JDBC 는 도메인주도설계의 개념을 차용해 개발되었다.

이 내용이 가져오는 특징은 이 레퍼런스에 명확하게 적혀있지 않지만, 왜 Spring Data JDBC 에서는 @ManyToOne 연관관계가 없을까? 어떻게 AggreagteRoot 의 불변성을 지킬 것인가? 이런 고민을 하면서 왜 내용을 다시 읽어보면 어느 정도 납득되는 부분 생깁니다.

먼저 @ManyToOne 이 없는 이유는 DDD 에서 말하는 AggregateRoot 와 연관되어 있습니다. 도메인주도설계에서는 Aggregate 라는 용어가 등장합니다. Aggregate 는 연관된 성질을 가진 엔티티의 집합이라고 생각하면 됩니다. Aggregate 를 사용하는 이유는 데이터가 변경되더라고, Aggregate 안에서는 데이터가 일관성을 가질 수 있도록 결과적 일관성을 보장하는데 있습니다. 데이터의 변경은 오직 Aggregate 안에서 일어나야 하며, 만약 서로 다른 Aggregate 의 데이터 변경이 필요하다면- AggregateRoot 가 되는 엔티티를 통해서만 접근되어져 수정이 이루어져야 합니다.

@ManyToOne 은 위 언급한 결과적일관성을 깨질 수 있습니다. 서로 다른 Aggregate 를 가로지르는 행위를 가져오기 때문에 Spring Data JDBC 에서는 사용되지 않는 것입니다.

그렇기 때문에 Spring Data JDBC는 사용하는 개발자에게 하여금 연관 관계에 대해서 명확하게 할 수 있도록 강제하고 있다는 생각이 들었습니다. Entity 하나가 가져야할 속성과 행위를 집중하는 것이 시작으로, 서로다른 Entity 가 어떤 관계를 가져야 하는지? 실제로 JDBC 를 Spring data JDBC로 변환하는 과정에서 JPA에서 발견할 수 없었던 연관관계의 문제점을 Data JDBC 로 변경하게 되면서 찾을 수 있었고, 제가 판단하기로 더 나은 코드로 이어질 수 있었다고 생각합니다.

반면에 Spring Data JDBC 를 사용하는데 있어 분명한 단점도 존재합니다. 위 장표에서 노란색으로 표현되어 부분으로, 현재 구현상 엔티티의 참조는 삭제되었다가 다시 만들어진다는 내용이 있습니다. 만약 엔티티의 참조가 1000개 있다면, JDBC는 1000개의 모든 데이터를 모두 지우고, 다시 생성하는 방식으로 데이터를 수정합니다. 개발자로 하여금 불필요한 쿼리라고 생각할 수 있는 부분이라고 생각합니다.

[참고 자료]

  1. https://stackoverflow.com/questions/54416949/how-to-select-referenced-entities-with-spring-data-jdbc
  2. https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#jdbc.repositories
  3. https://velog.io/@rla36/Spring-Data-JDBC-2-%EA%B4%80%EA%B3%84-%EC%84%A4%EC%A0%95
  4. https://homoefficio.github.io/2019/04/28/JPA-%EC%9D%BC%EB%8C%80%EB%8B%A4-%EB%8B%A8%EB%B0%A9%ED%96%A5-%EB%A7%A4%ED%95%91-%EC%9E%98%EB%AA%BB-%EC%82%AC%EC%9A%A9%ED%95%98%EB%A9%B4-%EB%B2%8C%EC%96%B4%EC%A7%80%EB%8A%94-%EC%9D%BC/

끝!

댓글