티스토리 뷰

728x90
반응형

 

 

 

지난 글에선 SQL Mapper와 ORM의 차이에 대해서 알아봤다.

 

글 마지막에 ORM은 SQL Mapper의 많은 단점을 보완해주지만 배우기가 어렵다고 했다.

 

실제로 객체 지향 프로그래밍의 개념과 관계형 데이터베이스를 다 알아야 해 러닝 커브가 높다.

 

특히 JPA가 어려우면서도 실무에서 많이 쓰이기 때문에, 배우는데 시간이 많이 든다고 한다.

 

이번 글에선 Spring Data 프로젝트의 라이브러리 중 하나인 Spring Data JDBC의 동작 방식을 살핀다.

 

 

Spring Data

 

Spring Data는 DB의 특성을 유지하면서 데이터에 접근하기 위한 Spring 기반 프로그래밍 모델을 제공하는 프로젝트다.

 

RDB뿐 아니라 NoSQL의 데이터에도 쉽게 접근할 수 있으며, 클라우드 기반의 서비스도 쉽게 사용할 수 있게 해 준다.

 

그 밖의 특징은 아래와 같다.

 

  • 강력한 레포지토리 패턴과 커스텀 객체 매핑 추상화
  • 레포지토리 패턴의 메서드 이름으로 동적 쿼리 파생
  • 기본 속성을 제공하는 도메인 기본 클래스 구현
  • 이벤트 상태 지원 (생성, 마지막 변경 등)
  • 커스텀 레포지토리 코드 통합
  • JavaConfig 클래스 및 커스텀 XML 네임스페이스를 통한 스프링 통합
  • Spring MVC 컨트롤러와의 향상된 통합
  • 상호간 영속화 지원 (Experimental)

또한 위 그림에서 볼 수 있듯이 많은 라이브러리를 지니고 있는데, 그 일부를 적으면 아래와 같다.

 

  • Spring Data Commons - 모든 Spring Module을 뒷받침하는 핵심 Spring 개념
                                                        (CrudRepository, PagingAndSortingRepository 인터페이스)
  • Spring Data REST - 스프링 데이터 저장소들을 hypermedia 기반의 Restful 리소스로 export 해주는 모듈
  • Spring Data JPA - JPA를 위한 스프링 데이터 저장소 지원 (JpaRepository 인터페이스, SimpleJpaRepository 클래스)
  • Spring Data JDBC - spring-jdbc에 대한 Spring Data 추상화를 제공하는 모듈
                                             (CrudRepository를 구현하는 SimpleJdbcRepository 클래스)
  • Spring Data KeyValue - 스프링 데이터 모듈을 구축하는 저장소와의 SPI를 기반으로 쉽게 Key-Value DB 사용
  • Spring Data MongoDB - MongoDB를 위한 스프링 기반 객체 문서 지원 및 저장소
  • Spring Data Redis - Spring Application에서 Redis 손쉽게 구성  접근할  있도록 하는 모듈

 

Spring Data JDBC?

 

Spring Data JDBC는 위에도 적었듯이 자바 객체와 DB Table을 매핑하기 위해 제공되는 라이브러리이다.

 

Spring Data JPA가 가진 복잡한 부분을 많이 덜어낸, DB를 조회하는 것에 초점을 둔 라이브러리라고 볼 수 있다.

 

JDBC Template을 제공하여 DB를 조회할 수 있고 transactionManager를 지원하지만

 

Hibernate 기반 영속성이나 1,2차 캐시, ddl auto create 같은 schema 관리는 지원하지 않는다.

 

이는 Spring Data JDBC가 Hibernate ORM을 사용하지 않기 때문이며, 아래 그림을 보면 좀 더 명확하다.

 

계속해서 지난 글까지 구현해놓은 코드를 사용해 Spring Data JDBC의 동작 방식을 살펴보자.

 

구현 코드에 Spring Data JDBC 사용하기

 

이 글의 목표는 클라이언트 쪽에서 “Hello, World” 문자열 데이터를 Request Body로 전송하고,

 

Spring Data JDBC를 이용해서 이 “Hello, World” 문자열을 H2 데이터베이스에 저장하는 것이다.

 

이후로 생성할 테스트 클래스는 src/main/java/com/gnidinger 안에 hello_world 패키지를 만들어 진행한다.

 

  • 의존 라이브러리 추가

Spring Data JDBC를 이용하기 위해 build.gradle에 의존성을 추가한다.

dependencies {
	...
	implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
	runtimeOnly 'com.h2database:h2'
}

Spring Data JDBC를 사용하기 위해 ‘spring-boot-starter-data-jdbc’를 추가하고

 

인 메모리(In-Memory) DB인 H2를 사용하기 위해 'com.h2database:h2'를 추가했다.

 

인 메모리(In-Memory) DB

 

인메모리(In-Memory) DB는 이름 그대로 메모리 안에 데이터를 저장하는 데이터베이스이다.

 

애플리케이션이 실행되는 동안에만 데이터를 저장하고 있으며, 앱을 껐다 켜면 데이터는 모두 사라진다. 

 

테스트 진행 시 필요한 데이터 이외의 데이터는 테이블에 없는 것이 정확도 면에서 유리하기 때문에 사용되며,

 

이와 같은 이유 때문에 로컬 개발 환경에서는 인메모리(In-Memory) DB를 주로 사용하게 된다.

 

  • application.yml, schema.sql 생성 및 설정

Spring Boot Initializr를 통해 프로젝트를 생성하면 ‘src/main/resources’ 안에 application.properties 파일이 생성된다.

 

.yml을 사용하는 파일이 속성 입력에 더 나은 점이 있기 때문에 파일 이름을 application.yml로 바꿔준 뒤 설정을 추가한다.

spring:
  h2:
    console:
      enabled: true
      path: /h2     // Context path 설정
  datasource:
    url: jdbc:h2:mem:test     // JDBC URL 설정

이제 웹 브라우저 상(H2 콘솔)에서 H2 DB에 접속한 후, 데이터베이스를 관리할 수 있게 되었다.

 

코드 실행 시 보이는 화면이다. 가장 마지막 줄을 보면 H2 콘솔의 접속 URL Context path가 위에서 설정한 /h2이며,

 

JDBC URL이 ‘jdbc:h2:mem:test’로 고정된 것을 확인할 수 있다.

 

JDBC URL의 경우 고정시키지 않으면 실행시마다 랜덤 값을 생성하게 된다.

 

이제 H2 DB에 접속해보자.

 

위와 같이 접속한 뒤에 JDBC URL에 위에서 설정한 jdbc:h2:mem:test를 입력하고 Connect를 누른다.

 

위와 같은 화면이 나오면 성공한 것이다.

 

이어서 데이터를 담을 테이블을 추가해보자.

spring:
  h2:
    console:
      enabled: true
      path: /h2     // Context path 설정
  datasource:
    url: jdbc:h2:mem:test     // JDBC URL 설정
    sql:
      init:
        schema-locations: classpath*:db/h2/schema.sql   // 테이블 스키마 파일 경로

위와 같이 스키마 파일의 경로를 추가해주면 앱 실행 시 파일을 읽어 테이블을 자동 생성해 준다.

 

스키마 파일은 src/main/resources/db/h2에 생성한다.

CREATE TABLE IF NOT EXISTS MESSAGE (
    message_id bigint NOT NULL AUTO_INCREMENT,
    message varchar(100) NOT NULL,
    PRIMARY KEY (message_id)
);

message_id를 기본키(Primary Key)로 지정해 AUTO_INCREMENT를 통해 자동 증가되도록 설정했다.

 

추가로 MESSAGE는 잠시 후 생성할 엔티티 클래스 이름과 매칭 된다.

 

이제 기본 준비는 끝이다. 본격적으로 클래스를 생성해 보자.

 

  • 테스트 클래스 생성
package com.gnidinger.hello_world;

import lombok.Getter;

import javax.validation.constraints.NotBlank;

@Getter
public class MessagePostDto {
    @NotBlank
    private String message;
}

클라이언트가 전달할 "Hello, World" 바인딩할 DTO클래스이다.

package com.gnidinger.hello_world;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class MessageResponseDto {
    private long messageId;
    private String message;
}

이어서 Response 사용할 DTO 클래스이다.

package com.gnidinger.hello_world;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RequestMapping("/v1/messages")
@RestController
public class MessageController {
    private final MessageService messageService;
    private final MessageMapper mapper;

    public MessageController(MessageService messageService, MessageMapper mapper) {
        this.messageService = messageService;
        this.mapper = mapper;
    }

    @PostMapping
    public ResponseEntity postMessage(@Valid @RequestBody MessagePostDto messagePostDto) {
        Message message =
                messageService.createMessage(mapper.messageDtoToMessage(messagePostDto));
        
        return ResponseEntity.ok(mapper.messageToMessageResponseDto(message));
    }
}

다음으로는 컨트롤러 클래스이다.

package com.gnidinger.hello_world;

import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface MessageMapper {
    Message messageDtoToMessage(MessagePostDto messagePostDto);
    MessageResponseDto messageToMessageResponseDto(Message message);
}
package com.gnidinger.hello_world;

import org.springframework.data.repository.CrudRepository;

public interface MessageRepository extends CrudRepository<Message, Long> {
}

계속해서 Mapper 인터페이스와 Repository 인터페이스를 작성했다.

 

레포지토리 인터페이스가 상속받은 CrudRepository데이터베이스에 CRUD작업을 하기 위해

 

스프링에서 지원해주는 인터페이스이다.

 

CrudRepository<Message, Long>과 같이 제네릭 타입을 지정해 줌으로써

 

Message(엔티티) 클래스 객체에 담긴 데이터를 DB Table에 올리거나

 

DB Table에서 읽어온 데이터를 엔티티 클래스로 변환할 수 있게 된다.

 

이중 Long은 엔티티 클래스의 멤버 중 기본키(Primary Key)를 의미하는 변수의 데이터 타입이 될 것이다.

package com.gnidinger.hello_world;

import org.springframework.stereotype.Service;

@Service
public class MessageService {
    private MessageRepository messageRepository;
    
    public MessageService(MessageRepository messageRepository) { // DI
        this.messageRepository = messageRepository;
    }
    
    public Message createMessage(Message message) { // 엔티티 클래스 -> DB에 저장
        return messageRepository.save(message);
    }
}

생성한 레포지토리 인터페이스를 서비스 클래스에 주입해,

 

createMessage()를 통해 엔티티 클래스의 데이터를 DB에 저장 후 리턴하는 것을 확인할 수 있다.

 

여기서 쓰인 save() 메서드는 위에서 상속받은 CrudRepository에 정의되어 있으며,

 

별도의 코드를 구현하지 않아도 CRUD 작업을 손쉽게 해결할 수 있다.

package com.gnidinger.hello_world;

import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;

@Getter
@Setter
public class Message { // schema.sql의 Table 이름과 동일
    @Id // Primary Key 역할
    private long messageId;
    private String message;
}

마지막으로 Message 엔티티 클래스이다.

 

위에서 설명한 바와 같이 클래스의 이름과 schema.sql의 Table 이름을 맞춰주었으며

 

@Id 애너테이션을 통해 고유 식별자(Primary Key)를 지정한 것을 확인할 수 있다.

 

이제 애플리케이션을 실행시키고 요청을 전송해 보자.

 

응답 결과 200으로 잘 전달이 돼서 DB에 저장된 것을 확인할 수 있다.

 

또한 처음에 말한 것과 같이 messageId는 자동으로 생성되어 순서대로 번호가 붙고 있다.

 

H2 콘솔로 돌아가 테이블에 저장된 데이터를 확인하자.

 

성공적으로 저장된 것을 확인할 수 있다.

 

  • 요약

이 글에서 살펴본 Spring Data JDBC의 적용 순서를 요약한다.

 

  1. build.gradle 의존 라이브러리 추가
  2. application.yml 파일에 DB 대한 설정 추가
  3. schema.sql 파일에 Table 스크립트 작성
  4. application.yml 파일에 schema.sql 파일을 읽어와 Table 생성할 있도록 초기화 설정을 추가
  5. DB의 Table 매핑할 엔티티(Entity) 클래스 작성
  6. 작성한 엔티티 클래스를 기반으로 DB 작업을 처리할 Repository 인터페이스 작성
  7. 작성된 Repository 인터페이스를 Service 클래스에서 사용할 있도록 DI
  8. DI Repository 메서드를 사용해서 Service 클래스에서 DB CRUD 작업 수행
반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/06   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30
글 보관함