[Spring] Kotlin + Spring boot 에 JPA 적용

 

앞글 시나리오에 단어를 추가하고 싶고 지속적인 저장을 하고 싶다는 요구사항을 추가하면 어떻게 될지 고민해 보자.

위의 요구사항을 만족시키려면 데이터를 Database에 저장해야 한다. Database와 연동을 ORM 기반으로 구현할 예정이고 ORM 기반으로 구현하려면 오늘의 주인공 JPA가 필수 이다.

이 블로그에서 특별한 설명이 없으면 Database는 PostgreSQL을 기본으로 사용한다.

JPA(JAVA Persistence API)

이름 그대로 ORM기술에 대한 JAVA 명세다. JPA는 딱 interface 역할만 하고 그 이후의 처리는 Hibernate 같은 구현체를 사용하여 구현한다.

Hibernate

JAVA base로 한 ORM 구현체이다. python에 Sqlalchemy가 있듯이 말이다. 찾아보니 여러가지 ORM 구현체가 있는데 Hibernate가 제일 많이 쓰이는 것 같다. Hiberate는 JDBC layer와 application layer 사이 추상화 층을 추가하여 DB를 더욱 쉽게 다둘 수 있게 만들어 준다.(ORM 기본) 여기를 통해 더 자세한걸 알 수 있다.

PostgreSQL

Enterprise 급 open source Database 이다. 기본적인 기능과 사용법이 Oracle과 비슷하다고 소개 되었다. 필자가 제일 익숙한 DBMS 이기때문에 현재는 특별한 설명이 없으면 모든 글은 PostgreSQL을 사용한다. 좀 더 자세히 파악하고 싶다면 이글을 추천 드린다

JPA 적용

JPA을 적용에 앞서 PostgreSQL 서비스를 올려야 한다. Docker 기반으로 PostgreSQL 서비스를 하는법은 여기를 참조하시기 바랍니다.

Gradle 설정

JPA을 적용하려면 앞의 글에서의 설정 정보를 그대로 사용하면 컴파일 에러가 발생한다. dependencies에 jpa에 대한 종속성을 추가 한다.

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
	id("org.springframework.boot") version "2.5.6"
	id("io.spring.dependency-management") version "1.0.11.RELEASE"
	kotlin("jvm") version "1.5.31"
	kotlin("plugin.spring") version "1.5.31"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
	runtimeOnly("org.postgresql:postgresql")
	implementation("org.hibernate.validator:hibernate-validator:6.2.0.Final")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
	kotlinOptions {
		freeCompilerArgs = listOf("-Xjsr305=strict")
		jvmTarget = "11"
	}
}

tasks.withType<Test> {
	useJUnitPlatform()
}

Dababase Entity 정의

ORM를 사용하여 개발하려면 먼저 Entity부터 정의해야 한다. 위 시나리오대로라면 하나의 Entity면 충분할것 같다. 저번 글에서 프로젝트 구조를 어느 정도는 잡아 놓았다. 지금부터 Entity을 프로젝트에 model 폴더를 만들고 그 밑에 넣어보자.

/src/main/kotlin/com.example.demo/Model/word.kt

@Entity
class Word {
    @Id
    @GeneratedValue(generator = "word_generate")
    var id: Long? = null

    var name: String? =null
}

key가 아이디이고 name이 단어인 테이블을 정의 했다.

JPA 사용

앞글에서 작성했던 WordRepostitory를 JPA 기반으로 바꾸자. JPA로부터 상속받아서 Interface만 정의하면 기본적인 CRUD는 Hibernate에서 구현해준다.

/src/main/kotlin/com.example.demo/Repository/WordRepository.kt

interface WordRepository: JpaRepository<Word,Long>{
    fun findByName(name:String):List<Word>
}

Repository 수정과 함께 Service 쪽도 수정이 필요하다. 서비스에서는 위에서 생성한 WordRepository 함수를 이용하여 단어를 검색해야 한다.

/src/main/kotlin/com.example.demo/Service/SearchService.kt

@Service
class SearchService(
    private val wordRepository: WordRepository
) {
    fun search_word(word: String):List<Word>{
        return wordRepository.findByName(word)
    }
}

여기서 잠깐 앞에 글과 비교했을경우 앞의 경우는 로직으로 개발자가 구현하는 반면에 여기서는 JPA과 Hibernate를 이용하여 좀 더 쉽게 구현 할 수 있다.(필드로 검색 기능은 기본적으로 Interface만 정의하면 자동 구현 된다.)

마지막으로 Controller를 아래와 같이 변경한다.

/src/main/kotlin/com.example.demo/Repository/WordRepository.kt

@RestController
class SearchController(
    @Autowired var searchService: SearchService,
){
    @GetMapping("search")
    fun search_word(@RequestParam word: String): List<Word>{
        return searchService.search_word(word)
    }

}

@RestController
class WordController(
    @Autowired var wordRepository: WordRepository
)
{
    @PostMapping("words")
    fun save_word(@RequestBody word: Word): Word{
        return wordRepository.save(word)
    }
}

여기서는 직관적으로 코드를 읽기 위하여 Controller class를 두개를 정의하였다.

먼저 두번째 WordController class부터 보면 post 방식으로 새로운 단어를 받는 것을 확인 할 수 있다.

본 글의 요구사항을 만족시키는 부분이다. 또한 검색과 단어를 두개의 Rest API로 구분하여 사용했다.

모든 준비는 끝났다. 마지막으로 resources 폴더 아래 application.properties에 아래처럼 DB 접속 정보를 넣는다.

## Spring DATASOURCE (DataSourceAutoConfiguration & DataSourceProperties)
spring.datasource.url = jdbc:postgresql://localhost:5432/word
spring.datasource.username = Test
spring.datasource.password = Test1234

# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect

## Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update 

Ref: 

https://jsonobject.tistory.com/462

 

Spring Boot, Spring Data JPA, Querydsl로 타입 세이프 쿼리 작성하기

개요 Java 진영에서 RDBMS에 접근하는 방법은 기존의 MyBatis가 지배하던 시대에서 JPA로 빠르게 이동하고 있다. 특히, Spring Data JPA 의 등장으로 불필요한 코드가 상당히 줄어들면서 새로 시작하는 프

jsonobject.tistory.com

https://www.javaguides.net/2018/11/hibernate-framework-overview-architecture-bascis.html

 

Hibernate Framework Overview - Architecture and Basics

Before getting started with Hibernate framework familiar with a few basic concepts of the hibernate framework, it's architecture, it's benifits, advantages over JDBC etx.

www.javaguides.net

https://d2.naver.com/helloworld/227936