데이터 액세스 계층 / DDD / JDBC

jungseo·2023년 6월 19일
0

Spring

목록 보기
7/23
post-thumbnail

데이터 액세스 기술 유형

1. SQL 중심 기술

  • 애플리케이션에서 데이터 베이스에 접근하기 위해 쿼리문을 애플리케이션 내부에 직접적으로 작성하는 것이 중심인 기술
  • mybatis
    • SQL Mapper 설정 파일에 쿼리문을 직접적으로 작성
    • 작성된 쿼리문을 기반으로 데이터베이스에서 데이터 조회 후 Java 객체로 변환
  • Spring JDBC

2. ORM(Object-Relational Mapping)

  • 데이터를 쿼리문이 아닌 객체 관점에서 다룸
  • 데이터베이스와 상호작용을 할 시 Java 객체를 이용해 쿼리문으로 자동 변환 후 데이터베이스에 접근
  • Spring Data JDBC
  • JPA(Java Persistence API)

DDD

  • 도메인 주도 설계(Domain Driven Design)

1. 도메인 : 비즈니스적인 어떤 업무의 영역, 분야

ex) 배달 주문앱의 도메인

2. 애그리거트(Aggregate)

  • 도메인들을 세분화하여 비슷한 업무 도메인들의 묶음

3. 애그리거트 루트(Aggregate Root)

  • 해당 애그리거트를 대표하는 도메인
  • 다른 도메인들과 직간접적으로 연관
  • DB와 비교했을때 애그리거트 루트는 부모 테이블, 다른 도메인들은 자식 테이블
  • 애그리거트 루트의 기본키 정보를 다른 도메인들이 외래키로 가지고 있는 형태

테이블 설계

  • 이전 게시물에서 만들어오던 커피 주문 프로그램의 엔티티 클래스 간 관계

  • 데이터 테이블 관계

  • 1 : N 관계인 경우 List 사용

    • DB의 테이블의 경우 관계는 외래키를 사용
    • 클래스 간의 관계는 객체 참조
  • N : N 관계인 경우 두 클래스 사이에서 각각 1 : N 관계로 만들어주는 클래스 추가

  • 도메인 간의 관계와 유사


JDBC

  • JDBC(Java Database Connectivity)
  • java 기반 애플리케이션의ㅏ 코드 레벨에서 사용하는 데이터를 데이터베이스에 저장, 업데이트 하거나 데이터베이스에 저장된 데이터를 코드 레벨에서 사용할 수 있게 해주는 java에서 제공하는 표준 사양
  • JDBC를 이용해 다양한 데이터베이스와 연동 가능

1. JDBC 동작 흐름

Java 애플리케이션에서 JDBC API를 이용해 데이터베이스 드라이버를 로딩 후 데이터베이스와 상호작용

1) Java 애플리케이션

2) JDBC API

3) JDBC 드라이버

4) 데이터베이스

2. JDBC API 사용 흐름

1) JDBC 드라이버 로딩

  • DriverManager 클래스를 통해 로딩

2) Connection 객체 생성

  • JDBC 드라이버가 로딩되면 DriverManager를 통해 데이터베이스와 연결되는 세션인 Connection 객체 생성
  • Connection Pool
    • Connection 객체를 미리 만들어 보관하고 애플리케이션이 필요할때 Connection 객체를 제공해주는 Connection 관리자
    • 기본 DBCP : HikariCP

3) Statement 객체 생성

  • 작성된 SQL 쿼리문을 실행하기 위한 객체
  • 생성 후 정적 SQL 쿼리 문자열을 입력으로 가짐

4) Query 실행

  • 생성된 Statement 객체를 이용해 입력한 SQL 쿼리를 실행

5) ResultSet 객체로부터 데이터 조회

  • 실행된 SQL 쿼리문에 대한 결과 데이터 셋

6) ResultSet, Statement, Connection 순으로 close


Spring Data JDBC

1. 기술 적용 순서

  1. build.gradle에 사용할 데이터베이스를 위한 의존 라이브러리를 추가
  2. application.yml 파일에 사용할 데이터베이스에 대한 설정
  3. ‘schema.sql’ 파일에 필요한 테이블 스크립트를 작성
  4. application.yml 파일에서 ‘schema.sql’ 파일을 읽어서 테이블을 생성할 수 있도록 초기화 설정을 추가
  5. 데이터베이스의 테이블과 매핑할 엔티티(Entity) 클래스를 작성
  6. 작성한 엔티티 클래스를 기반으로 데이터베이스의 작업을 처리할 Repository 인터페이스를 작성
  7. 작성된 Repository 인터페이스를 서비스 클래스에서 사용할 수 있도록 DI
  8. DI 된 Repository의 메서드를 사용해서 서비스 클래스에서 데이터베이스에 CRUD 작업을 수행

2. In-Memory DB

  • 연습을 위해 인메모리 DB H2 사용
  • build.gradle의 dependencies에 h2 의존 라이브러리 추가
dependencies {
	...
    implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	runtimeOnly 'com.h2database:h2'
}
  • application.properties 파일을 설정 정보를 depth 별로 입력할 수 있는 application.yml로 변경
  • h2 설정 추가
spring:
  h2:
    console:
      enabled: true
  • http://localhost:8080/h2-console 접속
  • JDBC URL 입력창에 프로젝트 실행 후 출력되는 로그
    H2 console available at '/h2-console'. Database available at 'jdbc:h2:mem: ...'
    'jdbc:h2:mem: ...' 부분을 입력
  • h2 콘솔 접속 URL 바꾸기
    • application.yml 파일에 설정 추가
spring:
  h2:
    console:
      enabled: true
      path: /h2     # (1) Context path 변경
  datasource:
    url: jdbc:h2:mem:test     # (2) JDBC URL 변경, 해당 URL로 접속 가능

3. 애그리거트 객체 매핑

  • 설계한 도메인 엔티티 클래스 관계를 DDD의 애그리거트 매핑 규칙에 맞게 변경 필요
  • 애그리거트 객체 매핑 규칙
    • 1) 모든 엔티티 객체는 애그리거트 루트를 통해서만 변경할 수 있다.
      • 도메인 규칙의 일관성 유지를 위해
    • 2) 하나의 동일한 애그리거트 내에서의 엔티티 간에는 객체로 참조한다.
    • 3) 애그리거트 루트 간의 참조는 객체 참조 대신 ID로 참조한다.
      • 1:1 관계, 1:N 관계일 때 테이블 간 외래키 방식과 동일
      • N:N 관계일 때 외래키 방식인 ID 참조와 객체 참조방식 함께 사용

4. 엔티티 구현


1) Member 클래스와 Order 클래스 매핑(1 : N 관계)

  • @Table 애너테이션이 없으면 클래스명이 테이블 이름과 매핑
  • @Id 애너테이션으로 식별자 지정

2) Order 클래스와 Coffee 클래스 매핑(N : N 관계)

  • Order 클래스와 Coffee 클래스는 N:N 관계로 중간에서 1:N, N:1로 풀어줄 OrderCoffee 엔티티는 Order 클래스와 동일한 애그리거트
  • Order 클래스에서 Set< OrderCoffee>를 사용해 1:N관계 설정
  • @MappedCollection 애너테이션
    • 엔티티 클래스 간에 연관 관계를 맺어주는 정보
    • idColumn : 자식 테이블에 추가되는 외래키에 해당되는 열명을 지정
      • 예시 코드의 경우 ORDER_COFFEE 테이블은 ORDERS 테이블의 기본키인 ORDER_ID를 외래키로 가짐
    • keyColumn : 외래키를 포함하고 있는 테이블의 기본키 열명을 지정
@MappedCollection(idColumn = "ORDER_ID", keyColumn = "ORDER_COFFEE_ID")
  • Set 자료구조의 경우 keyColunm은 생략 가능하지만 List, Map 일 경우 생략 불가능

3) 테이블 생성 스크립트

  • 로컬환경에서 인메모리 DB를 사용해 테스트 할 경우 테이블을 DROP 했다가 다시 생성할 필요 없이 테스트 가능
CREATE TABLE IF NOT EXISTS ORDERS (
    ORDER_ID bigint NOT NULL AUTO_INCREMENT,
    MEMBER_ID bigint NOT NULL,
    ORDER_STATUS varchar(20) NOT NULL,
    CREATED_AT datetime NOT NULL,
    PRIMARY KEY (ORDER_ID),
    FOREIGN KEY (MEMBER_ID) REFERENCES MEMBER(MEMBER_ID)
);

5. Repository 인터페이스 정의

  • Repository : 데이터 엑세스 계층에서 데이터베이스와 상호작용하는 역할을 하는 인터페이스
public interface MemberRepository extends CrudRepository<Member, Long> {
       // (2)
      Optional<Member> findByEmail(String email);
}

       // (3)
    @Query("SELECT * FROM MEMBER WHERE MEMBER_ID = :memberId")
    Optional<Member> findByMember(Long memberId);
  • (1) CrudRepository : crud 메서드를 제공하는 인터페이스
    • <엔티티 클래스, @Id 애너테이션이 붙은 멤버 변수의 타입>
  • (2) Spring Data JDBC에서 지원하는 쿼리 메서드

    ‘find + By + SQL 쿼리문에서 WHERE 절의 열명 + (WHERE 절 열의 조건이 되는 데이터) ’ 형식으로 쿼리 메서드(Query Method)를 정의하면 조건에 맞는 데이터를 테이블에서 조회

Optional<Member> findByEmail(String email);
  • 이 코드의 경우
    SELECT "MEMBER"."NAME" AS "NAME", "MEMBER"."PHONE" AS "PHONE", "MEMBER"."EMAIL" AS "EMAIL", "MEMBER"."MEMBER_ID" AS "MEMBER_ID" FROM "MEMBER" WHERE "MEMBER"."EMAIL" = ?
    로 변환되어 테이블에 질의

  • Spring Data JDBC에서는 Optional을 지원하기 때문에 리턴값을 Optional로 래핑하여 처리 가능

  • (3) @Query 애너테이션으로 직접 쿼리문 작성 가능

    • :memberId는 findByMember(Long memberId)의 memberId 변수 값으로 채워지는 동적 쿼리 파라미터

6. 서비스 클래스 구현 및 기타 코드 수정

  • 깃허브 참조

0개의 댓글