2023년 2월 19일 일요일

Spring Data JDBC 알아보기

아주 오랜만에 공부 글 작성...

스터디하면서 검색한 내용을 메모 

  •  스터디 issue : Spring Data 활용한 CRUD 구성

 

------------------------------------------------------

Spring Data

  • 데이터 저장소라는 특징은 유지하면서, 데이터 접근을 위한 친숙하고 일관된 Spring 기반의 프로그래밍 모델

    • 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자의 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌!
  • 데이터 엑세스 기술, 관계형/비관계형 데이터베이스, map-reduce 프레임워크(대용량 데이터 처리를 분산 병렬 컴퓨팅에서 처리하기 위한 목적), 클라우드 기반의 데이터 서비스 등을 쉽게 사용할 수 있게 해줌

    • 기존 관계형 DB 뿐만 아니라 폭 넓은 지원
  • 데이터베이스에 대한 하위 프로젝트들을 포함하는 상위프로젝트 개념

  • 특징

    • repository 메서드 이름에서 동적쿼리 파생
    • 기본 속성을 제공하는 기본 도메인 구현
    • auditing 지원 (created, last changed)
    • javaConfig or xml 을 통합 쉬운 Spring 설정
    • 향상된 Spring MVC 컨트롤러와의 통합
  • 주요 모듈

    • Spring Data Commons

      • 모든 Spring Data 모듈을 뒷받침하는 핵심 개념
      • Spring Data JPA 쓸 때 자주 사용하는 JpaRepsitory 를 타고 올라가다보면 결국 Spring Data Commons 을 볼 수 있음
      • repository
    • Spring Data JDBC

      • JDBC에 대한 Spring Data repository 지원
    • Spring Data JPA

      • JPA 에 대한 Spring Data repository 지원
    • Spring Data KeyValue, Spring Data MongoDB, Spring Data Redis, Spring Data REST 등

Spring Data 장단점

  • 장점

    • 다양한 데이터 저장소에 대한 접근을 추상화해서 개발자의 편의를 제공하고 지루하게 반복하는 데이터 접근 코드를 줄여줌
    • repository 메서드 이름에서 동적쿼리 파생
  • 단점

    • 어렵다 (각 하위 프로젝트들 사용에 있어서 학습시간이 필요함. 학습범위가 넓음)

Spring Data / JPA 구분 및 정리

구성

  • Spring Data

    • 위 내용과 같이 상위 프로젝트 개념
  • ORM

    • 객체 관계 매핑 (Object Relational Mapping)
    • 객체는 객체대로 설계
    • 관계형 데이터베이스는 관계형 데이터베이스대로 설계
    • 객체와 관계형 데이터베이스 를 ORM 프레임워크가 중간에서 매핑해준다
  • JPA

    • 자바 ORM 기술 표준. 인터페이스 모음
    • 구현체 : Hibernate, EclipseLink 등
    • (entityManager로 직접 persist, find 등 실행)
    • SQL 중심 개발 -> 객체 중심으로 개발하기 위함
    • 역사
      자바 과거 진영은 EJB 기슬 표준을 만들었는데 그 안에는 엔티티 빈이라는 ORM 기술도 포함되어 있었음
      하지만 너무 복잡하고 기술 성숙도도 떨어졌으며 자바 엔터프라이즈 애플리케이션 서버에서만 동작했음
      이때 하이버네이트라는 오픈소스 ORM 프레임워크가 등장했는데 EJB의 ORM 기술과 비교해서 가볍고 실용적인데 다 기술 성숙도도 높았음
      또한 자바 엔터프라이즈 애플리케이션 서버 없이도 동작해서 많이 사용하기 시작함
      결국 EJB 3.0 에서 하이버네이트를 기반으로 새로운 자바 ORM 기술 표준이 만들어짐 : JPA
      
    • JPA 사용 이유
      • 생산성
        • 자바 컬렉션에 객체를 저장하듯이 JPA에게 저장할 객체를 전달하면 됨
        • INSERT SQL을 작성하고 JDBC API를 사용하는 지루하고 반복적인 일은 JPA가 대신 처리해 줌
        • 지루하고 반복적인 CRUD용 SQL을 개발자가 직접 작성하지 않아도 됨
        • 이런 기능들을 사용하면 데이터베이스 설계 중심의 패러다임을 객체 설계 중심으로 역전시킬 수 있음
      • 유지보수
        • SQL에 의존적인 개발은 SQL을 직접 다루기 때문에 엔티티에 필드 하나만 추가해도 관련된 등록, 수정, 조회 SQL 과 결과를 매핑하기 위한 JDBC API 코드 모두 변경해야한다.
        • 반면 JPA를 사용하면 이런 과정을 JPA가 대신 처리해주므로 수정해야할 코드가 줄어든다.
        • 또한 JPA가 패러다임의 불일치 문제를 해결해주므로 객체지향 언어가 가진 장점들을 활용해서 유연하고 유지보수하기 좋은 도메인 모델을 편리하게 설계할 수 있음
      • 패러다임의 불일치 해결
        • JPA는 상속, 연관관계, 객체 그래프 탐색, 비교하기와 같은 패러다임의 불일치 문제를 해결해줌
      • 성능
        • JPA는 애플리케이션과 데이터베이스 사이에서 다양한 성능 최적화 기회를 제공함
        • 애플리케이션과 데이터베이스 사이에 계층이 하나 더 있으므로 최적화 관점에서 시도해볼 수 있는 것들이 많음
      • 데이터 접근 추상화와 벤더 독립성
        • 관계형 데이터베이스는 같은 기능도 벤더마다 사용법이 다른 경우가 많음
        • JPA는 애플리케이션과 데이터베이스 사이에 추상화된 데이터 접근 계층을 제공해서 애플리케이션이 특정 데이터베이스 기술에 종속되지 않도록 함
      • 표준
        • 자바 진영의 ORM 기술 표준임
        • 표준을 사용하면 다른 구현 기술로 손쉽게 변경할 수 있음
  • Hibernate

    • ORM 프레임워크
    • JPA의 구현체
    • 내부적으로 JDBC API를 사용

Spring Data JDBC vs Spring Data JPA 비교

  • Spring Data JDBC

    • Spring Data JDBC는 JDBC 기반 repositories 구현을 쉽게 해줌

    • JPA는 다양하고 편리한 기능을 제공하는데, 해당 기능이 단점이 될 수도 있음. 특정 작업이 수행되는 이유를 파악하기 어려울 수 있다. 단순한 개념도 JPA에선 어려울 수도 있다.

      • ex) 예상하지 못한 Entity 정보까지 가져올 수 있음
      • ex) lazy loading 발생이 언제하는 지 바로 파악하기 어려울 수 있음. 연관이 깊게 되어있는 경우 JPA가 제공하는 기능이 오히려 어려워질 수 있음
    • 그래서 Spring Data JDBC는 아래의 설계로 simple한 개념에 목표로 함

      • entity가 로드되는 시점에 SQL문이 실행되고 entity 로딩이 완료됨, 엔티티를 save 해야 저장된다
      • vs JPA
        • No EntityManager (PersistenceContext)
        • No Caching (1차 캐시)
        • No Lazy Loading
          • Aggregate를 정리하고 복합조회용 객체를 분리하면 Lazy loading이 필수일지 한번 더 생각해볼 수 있다.
          • 반대로 Lazy loading이 있어서 깊은 객체 그래프의 Aggregate를 설계하고자하는 유혹에 빠질 수도 있다.
          • Lazy loading이 복잡한 엔티티, 슈퍼 엔티티가 나올 수 있는 함정이 있음
          • Lazy loading이 필요하다는 것은 모델링을 다시 생각해봐야한다는 신호일 수도 있다
        • No Dirty Tracking
        • No Proxy
          • JPA에서는 dirty tracking, lazy loading, cache 이런 것들을 하기 위해 proxy 를 만들고 있음
        • 위의 것들은 Spring Data JDBC의 방향성과 맞지 않는다
    • Domain Driven Design

      • 모든 Spring Data 모듈은 Domain Driven Design 의 'repository', 'aggregate', 'aggregate root' 개념에서 영감을 얻었음
      • aggregate 는 원자적인 변경간에 일관성을 보장하는 엔티티의 그룹
        • Transaction, Lock의 필수 범위
        • 불변식(Invariants, 데이터가 변경될 때마다 유지돼야 하는 규칙)이 적용되는 단위
        • ex) OrderItems가 있는 Order
          • Order 속성은 변경 내용에 따라 일관성을 유지함
          • ex) Order의 numberOfItems 속성은 OrderItems 의 수와 일치함
      • 각 aggregate 에는 aggregate 의 엔티티 중 하나인 aggregate root 가 1개씩 있음
        • aggregate는 오직 aggregate root의 메서드를 통해서만 조작됨 -> 이것들이 앞서 언급된 원자적인 변경들임
        • root를 통해서만 조작이 되므로 일관성을 유지시킬 수 있음
      • 일반적으로 Spring Data의 경우 aggegate root당 1개의 Repository 를 가짐 (AGGREGATE 1개당 REPOSITORY 1개)
        • Spring Data JDBC 의 경우 aggregate root에서 접근할 수 있는 모든 엔티티가 해당 aggregate root의 일부로 간주됨을 의미함
        • aggregate root 를 통해서 aggregate 밖에서 aggregate 안의 객체로 접근함
      • aggregate 경계가 있는 시스템의 경우
        • 분리할 계획이 없더라도 코드를 고칠 때 영향성을 파악하기가 유리하다
        • 별도의 저장소나 API 서버를 분리할 때 상대적으로 유리
        • aggregate 별로 Cache 를 적용하기에도 좋다
      • aggregate 간의 참조
        • 다른 aggregate 의 root를 직접 참조하지 않고 ID로만 참조하기
        • Spring Data JDBC 에는 같은 역할을 하는 AggregateReference 라는 기능을 제공하고 있음 (id로 참조)
    • 프레임워크는 설계를 거드는 역할이 되어야 한다

      • 편의성이 주는 기능이 설계를 해치는 지 경계해야 한다
      • 때로는 다른 가치를 위해 더 긴 코드를 만들 수도 있다
        • 고치기 쉬운, 협업하기 쉬운, 확장하기 쉬운 코드를 위해서!
    • 구현체 : SimpleJdbcRepository

    • 구현 예시

  • Spring Data JPA

    • 스프링 프레임워크와 JPA 기반 위에 JPA를 편리하게 사용하도록 도와주는 기술
    • JPA를 추상화시킨 Repository 라는 인터페이스를 제공함
      • JpaRepository 구현체를 쭉 타고 들어가보면 SimpleJpaRepository 로 가는데 잠깐 소스 보면 entityManager를 쓰고 있음 (em.find(~~~))
      • (vs JDBC : SimpleJdbcRepository)
    • entity의 변경사항 추적 (lazy loading)
      • lazy loading: 필요 시점까지 리소스 로딩을 연기하다가 필요할 때 로딩하는 것
    • 구현체 : SimpleJdbcRepository

참고자료


 ------------------------------------------------------

DDD

  • DDD ?

    • Domain-Driven Design
    • 해당 도메인과 일치하도록 소프트웨어를 모델링하는 데 중점을 둔 소프트웨어 설계 접근 방식이다
    • 데이터 중심의 접근이 아니라 Domain 에 집중함
  • DDD 개념의 구성 요소 (DDD START 책)

    • ENTITY (엔티티)

      • 고유 식별자를 갖는 객체로 자신의 라이프 사이클을 갖는다.
        • 주문, 회원, 상품 과 같이 도메인의 고유한 개념을 표현함
      • 도메인 모델의 데이터를 포함하며 해당 데이터와 관련된 기능을 함께 제공한다.
      • 관계형 모델의 엔티티와 같은 것이 아님!
        • 도메인 모델의 엔티티는 데이터와 함께 도메인 기능을 함께 제공함
          public class Order {
            // 주문 도메인 모델의 데이터 
            private OrderNo number;
            private Orderer orderer;
            ...
          
            // 도메인 기능 제공
            public void changeShippingInfo(ShippingInfo shippingInfo) {
              ...
            }
          }
        • 단순히 데이터를 담고 있는 데이터 구조라기보다는 데이터와 함께 기능을 제공하는 객체
          • 도메인 관점에서 기능을 구현하고 기능 구현을 캡슐화해서 데이터가 임의로 변경하는 것을 막는다
    • VALUE (밸류)

      • 고유의 식별자를 갖지 않는 개체로 주로 개념적으로 하나인 값을 표현할 때 사용된다.
        • 배송지 주소를 표현하기 위한 주소나 구매 금액을 위한 금액과 같은 타입이 밸류 타입
      • 엔티티의 속성으로 사용할 뿐만 아니라 다른 밸류 타입의 속성으로도 사용할 수 있다.
    • AGGREGATE (애그리거트)

      • aggregate
      • 도메인이 커질 수록 많은 엔티티와 밸류가 출현하는데, 상위 수준에서 모델을 관리하지 않고 개별 요소에만 초점을 맞추다보면, 큰 수준에서 모델을 이해하지 못해 큰 틀에서 모델을 관리할 수 없는 상황에 빠질 수 있음
      • 도메인 모델에서 전체 구조를 이해하는 데 도움이 되는 것이 aggregate
      • aggregate는 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것이다.
        • 주문과 관련된 '주문', '배송지 정보', '주문자', '주문 목록', '총 결제 금액'의 하위 모델을 '주문' 이라는 상위 모델로 묶을 수 있다
      • aggregate를 사용하면 개별 객체가 아닌 관련 객체를 묶어서 객체 군집 단위로 모델을 바라 볼 수 있게 됨
      • aggregate root
        • aggregate는 군집에 속한 객체를 관리하는 루트 엔티티를 갖는다
        • 루트 엔티티는 aggregate에 속해 있는 엔티티와 밸류 객체를 이용해서 aggregate가 구현해야할 기능을 제공함
        • aggregate 를 사용하는 코드는 aggregate root가 제공하는 기능을 실행하고 aggregate root를 통해서 간접적으로 aggregate 내의 엔티티나 밸류 객체에 접근한다
          • ex) 주문 aggregate는 Order 를 통하지 않고는 ShippingInfo 를 변경할 수 있는 방법을 제공하지 않는다
        • aggregate root가 제공하는 메서드는 도메인 규칙에 따라 aggregate에 속한 객체의 일관성이 깨지지 않도록 구현해야 한다.
    • REPOSITORY (리포지터리)

      • 도메인 모델의 영속성을 처리한다
        • DBMS 테이블에서 엔티티 객체를 로딩하거나 저장하는 기능을 제공한다.
      • repository는 aggregate 단위로 도메인 객체를 저장하고 조회하는 기능을 정의함
        • Order 는 aggregate 에 속한 모든 객체를 포함하고 있으므로 결과적으로 aggregate 단위로 저장하고 조회함
      • aggregate는 개념상 완전한 한 개의 도메인 모델을 표현하므로 객체의 영속성을 처리하는 repository는 aggregate 단위로 존재한다.
        • ex) Order 와 OrderLine 을 물리적으로 각각 별도의 DB 테이블에 저장한다고해서 Order와 OrderLine을 위한 repository를 각각 만들지 않는다.
        • Order 가 aggregate root고 OrderLine은 aggregate에 속하는 구성요소이므로 Order를 위한 repository만 존재한다
    • DOMAIN SERVICE (도메인 서비스)

      • 특정 엔티티에 속하지 않은 도메인 로직을 제공한다
        • '할인 금액 계산'은 상품, 쿠폰, 회원 등급, 구매 등급 등 다양한 조건을 이용해서 구현하게 되는데, 이렇게 도메인 로직이 여러 엔티티와 밸류를 필요로 하면 도메인 서비스에서 로직을 구현한다.
  • 책에 나온 JPA 관련 내용

    • JPA는 @manytoone@OnetoOne 과 같은 애너테이션을 이용해 연관된 객체를 로딩하는 기능을 제공하고 있으므로 필드를 이용해 다른 aggregate를 쉽게 참조할 수 있다.
    • ORM 기술 덕에 aggregate root 에 대한 참조를 쉽게 구현할 수 있고 필드를 이용한 aggregate 참조를 사용하면 다른 aggregate 데이터를 쉽게 조회할 수 있다.
    • 하지만 필드를 이용한 aggregate 참조는 다음 문제를 야기할 수 있다
      • 편한 탐색 오용
        • 한 애그리거트 내부에서 다른 애그리거트 객체에 접근할 수 있으면 다른 애그리거트의 상태를 쉽게 변경할 수 있게 된다
        • 한 애그리거트에서 다른 애그리거트의 상태를 변경하는 것은 애그리거트 간의 의존 결합도를 높여서 결과적으로 애그리거트의 변경을 어렵게 만든다
      • 성능에 대한 고민
        • 지연로딩, 즉시로딩 고민
      • 확장 어려움
        • 부하를 분산하기 위해 하위 도메인별로 시스템을 분리할 때 다른 애그리거트 참조하기 위한 기술을 사용할 수 없음
    • 이런 세 가지 문제를 완화할 때 사용할 수 있는 것이 ID를 이용해서 다른 aggregate 를 참조하는 것이다.
      • ID 참조를 사용하면 모든 객체가 참조로 연결되지 않고 한 애그리거트에 속한 객체만 참조로 연결된다.
      • 이는 aggregate 의 경계를 명확히하고 aggregate 간 물리적인 연결을 제거하기 때문에 모델의 복잡도를 낮춰준다.
      • 참조하는 aggregate가 필요하면 응용 서비스에서 ID를 이용해서 로딩하면 된다.
      • ID를 이용한 참조 방식을 사용하면 복잡도를 낮추는 것과 함께 한 aggregate에서 다른 aggregate를 수정하는 문제를 근원적으로 방지할 수 있다.
      • aggregate 별로 다른 구현 기술을 사용하는 것도 가능해진다.
        • ex) 주문 aggregate는 RDBMS, 상품 aggregate 는 NoSQL 등

 ------------------------------------------------------

  1. 왜 Spring Data JDBC 는 Lazy Loading, Dirty Tracking 을 안할까?

    • Spring Data JDBC 는 ddd 철학을 가지고 나왔기 때문임
    • ddd는 Aggregate root 을 통해서만 변경을 추구함
    • JPA가 제공하는 편리한 기능은 예상하지 못한 곳에서 변경이 발생함. 일관성이 깨질 수도 있다?
    • Spring Data JDBC는 객체와 관계형 데이터베이스 간의 매핑을 최소화하기 위한 기술임. 따라서, 객체지향 프로그래밍에서 일반적으로 사용되는 Lazy Loading, Dirty Tracking과 같은 기능을 제공하지 않는다.
    • 그럼 Spring Data JPA 에는 왜 있을까?
      • 해당 기능은 영속성 컨텍스트(Persistence Context) 에 의해 가능함
      • 생긴 배경은, 객체-관계 매핑(ORM)이라는 기술과 연관 있는데 객체지향 프로그래밍 언어와 관계형 데이터베이스의 데이터 모델 사이의 불일치를 해결하기 위함
  2. Aggregate 랑 Aggregate root 차이점

    • Aggregate는 객체 모델링에서 도메인 객체들을 논리적으로 묶어서 관리하는 단위이며, Aggregate root는 Aggregate를 구성하는 루트 개체
    • Aggregate
      • 연관된 엔티티와 밸류 객체를 개념적으로 하나로 묶은 것
      • ex) 주문, 배송지 정보, 주문자, 주문목록, 총 결제 금액 등의 모델들을 '주문'이라는 상위 모델(aggregate)로 묶을 수 있다.
    • Aggregate root
      • 군집에 속한 객체를 관리하는 root
      • Aggregate root는 다른 객체에서 참조할 수 있는 진입점(entry point) 역할을 하며, Aggregate root를 통해서만 Aggregate 내부의 다른 객체에 접근할 수 있습니다. 이렇게 Aggregate root가 중심이 되어 Aggregate 내부의 객체들은 일종의 클로저(closure)처럼 동작함
      • Aggregate root는 또한 Aggregate 내부의 일관성(invariant)을 보장하는 책임도 가지고 있음. 즉, Aggregate root를 통해서만 Aggregate 내부의 객체들의 상태를 변경할 수 있으며, Aggregate root의 변경으로 인해 Aggregate 내부의 객체들의 상태가 불일치하는 경우를 방지함
      • root entity 는 Aggregate에 속해 있는 엔티티와 벨류 객체를 이용해서 Aggregate가 구현해야할 기능을 제공한다.
      • ex) 주문 Aggregate 는 Order(root) 을 통해서만 ShippingInfo를 변경할 수 있다. 이를 통해 ShippingInfo가 일관성 없게 바뀌는 것을 막아줌
  3. Spring Data JDBC는 왜 ManyToOne, ManyToMany 를 미지원할까?

    • Spring Data JDBC 는 Entity 설계 시 Aggregate 개념 적용을 강하게 주장한다.
    • Aggregate 개념에 위배된다 (필요하지 않은 개념이다)
      • Aggregate는 오직 Aggregate root의 메서드를 통해서만 조작된다. 다른 Aggregate 의 데이터 변경이 필요하다면 Aggregate root 를 통해서 접근되어서 수정이 발생해야 한다.
      • = 하나의 Aggregate는 하나의 Repository 를 통해서 영속성을 관리한다.
    • 위 Aggregate 개념에서 다른 Aggregate 객체 접근할 수 있으면 다른 Aggregate 의 상태를 쉽게 변경할 수 있다
      • 한 Aggregate에서 다른 Aggregate 상태를 변경하는 것은 Aggregate 간의 의존 결합도를 높여서 결과적으로는 Aggregate 의 변경을 어렵게 만든다