Spring Data CrudRepository로 데이터베이스 접근하기

Spring Data CrudRepository로 데이터베이스 접근하기

이번 단계에서는 서비스 계층을 데이터베이스 접근에 JdbcTemplate 대신 Spring DataCrudRepository를 쓰도록 마이그레이션할 거예요. CrudRepository는 특정 타입의 리포지토리에 대한 일반적인 CRUD 연산을 위한 Spring Data 인터페이스예요. 데이터베이스와 상호작용하는 여러 메서드를 기본으로 제공해 준답니다.

애플리케이션 업데이트하기

먼저 Message 클래스를 CrudRepository API와 함께 쓰도록 조정해야 해요.

  • Message 클래스에 @Table 어노테이션을 붙여 데이터베이스 테이블로의 매핑을 선언하고, id 필드 앞에 @Id 어노테이션을 붙여요.

참고: 이 어노테이션을 쓰려면 추가 import도 필요해요.

// Message.kt
package com.example.demo

import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table

@Table("MESSAGES")
data class Message(@Id val id: String?, val text: String)

추가로 Message 클래스를 더 관용적으로 쓰려면, id 프로퍼티의 기본값을 null로 설정하고 data class 프로퍼티의 순서를 바꿀 수도 있어요.

@Table("MESSAGES")
data class Message(val text: String, @Id val id: String? = null)

이제 Message 클래스의 새 인스턴스를 만들 때 text 프로퍼티만 파라미터로 지정하면 돼요.

val message = Message("Hello") // id is null
  • Message data class를 다룰 CrudRepository 인터페이스를 선언해요. MessageRepository.kt 파일을 만들고 다음 코드를 추가하세요.
// MessageRepository.kt
package com.example.demo

import org.springframework.data.repository.CrudRepository

interface MessageRepository : CrudRepository<Message, String>
  • MessageService 클래스를 업데이트해요. 이제 SQL 쿼리를 실행하는 대신 MessageRepository를 사용할 거예요.
// MessageService.kt
package com.example.demo

import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service

@Service
class MessageService(private val db: MessageRepository) {
    fun findMessages(): List<Message> = db.findAll().toList()

    fun findMessageById(id: String): Message? = db.findByIdOrNull(id)

    fun save(message: Message): Message = db.save(message)
}

확장 함수findByIdOrNull() 함수는 Spring Data JDBC에서 CrudRepository 인터페이스의 확장 함수예요.

CrudRepository의 save() 함수이 함수는 새 객체가 데이터베이스에 id가 없다는 가정하에 동작해요. 그래서 삽입(insert)을 위해서는 id가 null이어야 해요. id가 null이 아니면 CrudRepository는 객체가 이미 데이터베이스에 존재한다고 보고, 삽입이 아니라 업데이트(update) 연산으로 취급해요. 삽입 연산 후에는 id가 데이터 저장소에 의해 생성되고 Message 인스턴스에 다시 할당돼요.

  • 삽입되는 객체의 id를 생성하도록 messages 테이블 정의를 업데이트해요. id가 문자열이므로 RANDOM_UUID() 함수를 사용해 id 값을 기본으로 생성하면 돼요.
-- schema.sql 
CREATE TABLE IF NOT EXISTS messages (
    id      VARCHAR(60)  DEFAULT RANDOM_UUID() PRIMARY KEY,
    text    VARCHAR      NOT NULL
);
  • src/main/resources 폴더에 있는 application.properties 파일에서 데이터베이스 이름을 업데이트해요.
spring.application.name=demo
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:file:./data/testdb2
spring.datasource.username=name
spring.datasource.password=password
spring.sql.init.schema-locations=classpath:schema.sql
spring.sql.init.mode=always

다음은 애플리케이션의 전체 코드예요.

// DemoApplication.kt
package com.example.demo

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class DemoApplication

fun main(args: Array<String>) {
    runApplication<DemoApplication>(*args)
}

// Message.kt
package com.example.demo

import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table

@Table("MESSAGES")
data class Message(val text: String, @Id val id: String? = null)

// MessageRepository.kt
package com.example.demo

import org.springframework.data.repository.CrudRepository

interface MessageRepository : CrudRepository<Message, String>

// MessageService.kt
package com.example.demo

import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service

@Service
class MessageService(private val db: MessageRepository) {
    fun findMessages(): List<Message> = db.findAll().toList()

    fun findMessageById(id: String): Message? = db.findByIdOrNull(id)

    fun save(message: Message): Message = db.save(message)
}

// MessageController.kt
package com.example.demo

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
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 java.net.URI

@RestController
@RequestMapping("/")
class MessageController(private val service: MessageService) {
    @GetMapping
    fun listMessages() = ResponseEntity.ok(service.findMessages())

    @PostMapping
    fun post(@RequestBody message: Message): ResponseEntity<Message> {
        val savedMessage = service.save(message)
        return ResponseEntity.created(URI("/${savedMessage.id}")).body(savedMessage)
    }

    @GetMapping("/{id}")
    fun getMessage(@PathVariable id: String): ResponseEntity<Message> =
        service.findMessageById(id).toResponseEntity()

    private fun Message?.toResponseEntity(): ResponseEntity<Message> =
        // If the message is null (not found), set response code to 404
        this?.let { ResponseEntity.ok(it) } ?: ResponseEntity.notFound().build()
}

애플리케이션 실행하기

축하해요! 애플리케이션을 다시 실행할 준비가 됐어요. JdbcTemplateCrudRepository로 바꾼 뒤에도 기능은 그대로라서, 애플리케이션은 이전처럼 동작해요.

이제 requests.http 파일에서 POST·GET HTTP 요청을 실행해서 같은 결과를 얻을 수 있어요.

다음은 무엇?

Kotlin 기능을 탐색하고 학습 진도를 추적하는 데 도움이 되는 개인 언어 지도를 받아 보세요.

Kotlin 언어 지도 받기