Recommanded Free YOUTUBE Lecture: <% selectedImage[1] %>

Contents

이전 문서 Spring boot REST API 서버만들기 - 1 를 참고하자. 이전 문서에서는 딱 REST API를 호출하는 정도까지만 했다. 이제 데이터베이스 연결을 해볼 것이다. 이 문서는 강의가 아닌 학습 과정을 정리한 문서다.

JPA

JPA(Java Persistence API)는 Java 객체가 생성한 응용 프로그램의 프로세스보다 오래 지속되는 정보에 대한 관리 메커니즘을 의미한다. 데이터베이스가 가장 대표적인 경우가 될 것이다. JPA 그 자체는 도구나 프레임워크가 아니다. 도구 또는 프레임워크로 구현 할 수 있는 일련의 개념을 정의 한다.

예를 들어 JPA의 ORM(Object-relational mapping) 모델은 원래는 Hibernate를 기반으로 했지만 이후로도 계속 발전하고 있다. 마찬가지로 JPA는 원래 관계형(relational)/SQL 데이터베이스와 함께 사용하도록 고안되었지만 일부 JPA 구현은 NoSQL 데이터 저장소와 함께 사용하도록 확장되었다. NoSQL을 지원하는 JPA의 일반적인 프레임워크는 JPA 2.2의 참조 구현인 EclipseLink다.

JPA와 Hibernate

Gavin King이 개발하고 2002년 출시한 Hibernate는 Java용 ORM 라이브러리다. King은 entity beans의 대안으로 Hibernate를 개발했다. 이 프레임워크는 매우 인기가 있었고 또 필요했기 때문에 많은 아이디어가 JPA 사양에 채택되고 체계화되었다. 오늘날 Hibernate ORM은 가장 성숙한 JPA 구현 중 하나다. Hibernate ORM 5.3.8은 JPA 2.2를 구현하고 있다.

Java ORM

JPA 구현은 일종의 ORM 계층을 제공한다. 따라서 JPA와 JPA 호환 툴 들을 이해하려면 ORM에 대해서 잘 알고 있어야 한다.

Object-relation mapping은 개발자가 수동으로 해야 할 작업을 자동화 시킨다는 점에서 충분한 이유가 있는 작업이다. Hibernate ORM 또는 EclipseLink와 같은 프레임워크는 데이터베이스 작업을 프레임워크인 ORM 계층으로 코드화 시킨다. 이는 응용 프로그램 아키텍처의 일부로 ORM 계층을 둠으로써 관계형 데이터베이스의 테이블 및 컬럼을 소프트웨어 객체로 변환할 수 있다. 즉 Java 클래스 및 객체를 관계형 데이터베이스에 저장하고 관리 할 수 있게 된다.

기본적으로 persisted 객체 이름은 테이블 이름이 되고 필드는 컬럼이 된다.

 JPA Layer

최근들어 비 관계형 데이터베이스(non-relation databases)에 대한 관심이 높아지면서 Java 개발자들은(다른 언어 개발자도 마찬가지) 다양한 NoSQL 데이터베이스를 사용하고 있다. Hibernate OGM과 EclipseLink를 포함한 몇몇 JPA 구현은 NoSQL을 수용하도록 발전하고 있다.

JPA를 사용해서 새 포로젝트를 설장할 때는 데이터 저장소(datastore)와 JPA 제공자를 설정해야 한다. 선택한 데이터베이스(SQL 혹은 NoSQL)에 연결하도록 데이터 저장소 컨텍터를 구성한다. 또한 Hibernate나 EclipseLink와 같은 프레임워크를 사용하도록 구성 할 수 있다. JPA를 수동으로 구성할 수 있지만 많은 개발자들이 Spring에 맡기고 있다.

JPA 설정

다른 모든 현대적인 프레임워크와 마찬가지로 JPA도 컨벤션으로 코딩하는 방식을 적용하고 있다. 일례로 Musician이라는 클래스는 기본적으로 Musician이라는 데이터베이스 테이블에 매핑된다.

기존 구성에도 JPA를 사용 할 수 있는데, 얘를 들어 JPA의 @Table 어노테이션을 사용해서 Musician 클래스가 저장될 테이블을 지정 할 수 있다.
@Entity
@Table(name="musician")
public class Musician {
  // ..class body
}

Primary Key

JPA에서 primary key는 데이터베이스의 각 오브젝트를 유일하게 식별하기 위해서 사용하는 필드다. Primary key는 객체를 참조하고 다른 엔티티와 연결하는데 유용하다.
@Entity
public class Musician {
  @Id
  private Long id;
}
이 경우 JPA의 @Id 어노테이션을 사용하여 id 필드를 Musician의 프라이머리 키로 설정했다. 이 필드는 기본적으로 auto-increment로 설정된다.

CRUD 연산

클래스를 데이터베이스 테이블에 맵핑하고 프라이머리 키를 설정하면 데이터베이스에 대한 create, retrieve, delete, update 가 가능해진다.

Review API 개발

Review API를 개발한다. 테이블은 간단하게 만들었다.

 Review Table

JPA를 위한 의존성 추가

build.gradle에 "org.springframework.boot:spring-boot-stater-data-jpa" 와 "com.h2database:h2" 의존성을 등록한다.
dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}
spring-boot-stater-data-jpa
  • spring boot를 위한 Spring Data JPA 추상화 라이브러리. Spring boot 버전에 맞춰 자동으로 JPA 관련 라이브러리들의 버전을 관리해 준다.
H2
  • 인메모리 관계형 데이터베이스. 개발목적으로 사용했다.

H2 데이터베이스

H2는 Java 언어로 개발된 SQL 데이터베이스 관리 시스템으로 아래의 특징을 가진다.
  • 빠르다. 오픈소스다. JDBC API를 제공한다.
  • 임베디드 혹은 서버모드로 실행 할수 있는 인-메모리 데이터베이스
  • 웹 브라우저로 관리 할 수 있는 "웹 콘솔"을 제공한다.
  • 2MB 정도의 작은 크기
디버깅을 위해서 H2 데이터베이스 웹 콘솔을 활성화 하자. application.properties 에 아래 내용을 추가한다.
spring.h2.console.enabled=true

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:review
spring.datasource.username=yundream
spring.datasource.password=1234

Entity 클래스 생성

package co.kr.joinc.Todo.domain.review;

import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@NoArgsConstructor
@Entity
public class ReviewEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private Long UserId;

    @Column(columnDefinition = "TEXT", nullable = false)
    private String Content;
}
  1. @Entity : 테이블과 맵핑될 클래스임을 나타낸다. 클래스의 이름은 카멜케이스를 따른다. 이 이름은 데이터베이스에서는 언더바 네이밍으로 매칭된다. (ex. ReviewEntity -> review_entity)
  2. @GeneratedValue : PK 생성규칙. Spring boot 2.0 부터는 GenerationType.IDENTITY 옵션을 추가해야지만 auto_increment 가 된다.
  3. @Id : PK 필드임을 나타낸다.
  4. @Column : 테이블의 컬럼임을 나타낸다. 굳이 선언하지 않더라도 Entity 클래스의 모든 필드는 컬럼이 된다. 기본 값 외에 추가적인 변경이 필요하다면 이 어노테이션을 사용한다.
  5. @Builder : Entity 클래스의 빌더 패턴 클래스를 생성. 빌더 패턴을 찾아보자.
이렇게 ReviewEnity 클래스를 만들었다. 이제 이 클래스를 데이터베이스에 접근하게 해 줄 JpaRepository를 생성한다.
package co.kr.joinc.Todo.domain.review;

import org.springframework.data.jpa.repository.JpaRepository;

public interface ReviewRepository extends JpaRepository<ReviewEntity, Long>{
}
ibatis나 MyBatis에서 DAO로 부르는 데이터베이스 레이어 접근자다. JPA에서는 Repository라고 부르는 인터페이스로 만든다. JpaRepository<Entity Class, PK 타입>을 상속하면 기본적인 CRUD 메서드가 자동으로 생성된다. Entity 클래스와 Entity Repository는 함께 위치해야 한다.

등록 API 만들기

API를 만들기 위해서는 3개의 클래스가 필요하다.
  1. Request 데이터를 받을 DTO
  2. API를 받을 Controller
  3. 트랜잭선, 도메인 기능간의 순서를 보장하는 Service
Spring web의 계층은 아래와 같다.

 Spring 웹 계층

  • Web Layer : 컨트롤러(@Controller), freemarker등 뷰, 템플릿을 포함하는 영역이다. 이외에도 필터(@Filter), 인터셉터, 컨트롤러 어드바이스(@ControllerAdvice)등 외부 요청과 응답을 다룬다.
  • Service Layer : @Service를 다루는 영역이다. Controller와 DAO의 중간 영역에 위치한다. @Transactional도 포함한다.
  • Repository Layer : 데이터베이스 저장소에 접근하는 영역.
  • DTOs : 계층간 데이터 교환을 하는 객체
  • Domain Model : 특정 범위의 개발 대상을 모든 사람들이 동일한 관점에서 이해, 공유 할 수 있도록 단순화시킨 모델이다. 쇼핑몰 서비스라면 장바구니, 구매, 지불 시스템 등이 도메인이 될 수 있다. @Entity가 사용된 영역이 도메인 모델이다.
DTO를 만들어보자. 리뷰를 저장하는 DTO를 만들었다.
package co.kr.joinc.Todo.dto.review;

import co.kr.joinc.Todo.domain.review.ReviewEntity;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class ReviewPostRequestDto {
    private String content;
    private Long user_id;

    @Builder
    public ReviewPostRequestDto(String content, Long user_id) {
        this.content = content;
        this.user_id = user_id;
    }

    public ReviewEntity toEntity() {
        return ReviewEntity.builder()
                .content(content)
                .user_id(user_id)
                .build();
    }
}

Review Controller과 Service 등록

Review 서비스 클래스인 ReviewPostService 클래스를 만들었다.
package co.kr.joinc.Todo.service.review;

import co.kr.joinc.Todo.domain.review.ReviewRepository;
import co.kr.joinc.Todo.dto.review.ReviewPostRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@RequiredArgsConstructor
@Service
public class ReviewPostService {
    private final ReviewRepository reviewRepository;

    @Transactional
    public Long save(ReviewPostRequestDto requestDto) {
        return reviewRepository.save(requestDto.toEntity()).getId();
    }
}
  • @Transactional 어노테이션은 transaction begin, commit를 자동으로 수행한다.
  • 예외가 발생할 경우 rollback 처리를 자동으로 수행한다.
review controller 코드다.
package co.kr.joinc.Todo.controller.review;

import co.kr.joinc.Todo.dto.review.ReviewPostRequestDto;
import co.kr.joinc.Todo.service.review.ReviewPostService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
public class ReviewPostController {
    private final ReviewPostService reviewPostService;

    @PostMapping("/api/v1/review")
    public Long save(@RequestBody ReviewPostRequestDto requestDto) {
        return reviewPostService.save(requestDto);
    }
}

테스트

애플리케이션을 실행하고 H2 Console에 접근했다.

 H2 Database 로그인 화면

로그인 화면이 뜬다. application.properties에 설정한 datasource.{url,username,password}로 로그인 한다.

 H2 Console

review_entity 테이블과 각 필드들이 ReviewEntity Class와 맵핑되서 만들어진 것을 확인 할 수 있다.

curl 테스트용 데이터를 만들었다.
{
	"user_id": 1234,
	"content": "안녕하세요. 만나서 반갑습니다."
}

curl 테스트
# curl -XPOST -H "Content-Type: application/json" localhost:8080/api/v1/review -d @test.json

H2 웹 콘솔에서 데이터가 제대로 들어갔는지 확인해보자.
SQL> SELECT * FROM review_entity;

+------+---------------------------------+-----------+
| ID   | CONTENT                         | USER_ID   |
+------+---------------------------------+-----------+
| 1    | 안녕하세요. 만나서 반갑습니다.  | 1234      |
+------+---------------------------------+-----------+

잘 작동한다. 어쨋든 이렇게 해서 데이터베이스 연결까지 했다.

정리

  • 일단 데이터베이스 연결을 했다. 좀 정리하면, 완전한 CRUD 작업을 수행하는 애플리케이션을 만들 수 있을 것 같다. 다음 번에는 마무리 해야 겠다.
  • MySQL로 연결해봐야 겠다.
  • 테스트도 넣어야 할테고, 배포도 해야 겠지. 요즘 컨테이너로 배포하고 있으니 도커라이징도 해야 하겠다.
  • 기존에 사용하던 Go언어 대신에 Spring Boot 기반으로 REST API 서버를 개발 할 필요가 있는지는 여전히 의문이다. 그냥 Go 언어 사용하는게 더 나을 것 같다.

참고

  Spring Boot REST API 서버 만들기 1 - 시작