본문으로 바로가기

0. 환경

  • Spring Boot 2.7.16 (Gradle)
  • JDK 11(Java 11)
  • IntelliJ
  • Postman

1. Spring Boot 프로젝트 생성

https://start.spring.io 에 접속해 필요한 환경에 맞게 Spring Boot 프로젝트를 정의해 다운로드합니다.

start.spring.io

2. Project 열기(IntelliJ)

  • 압축을 풀어 둔 Spring Boot Project를 IntelliJ로 열어줍니다.
  • 라이브러리 다운로드가 필요해 시간이 좀 걸립니다.

IntelliJ

build.gradle.kts

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

plugins {
	id("org.springframework.boot") version "2.7.16"
	id("io.spring.dependency-management") version "1.0.15.RELEASE"
	kotlin("jvm") version "1.6.21"
	kotlin("plugin.spring") version "1.6.21"
	kotlin("plugin.jpa") version "1.6.21"
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
	sourceCompatibility = JavaVersion.VERSION_11
}

repositories {
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	runtimeOnly("com.h2database:h2")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

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

tasks.withType<Test> {
	useJUnitPlatform()
}

3. IntelliJ 설정

  • 프로젝트 실행 전 성능 등을 위해 간단한 IntelliJ 설정을 합니다. 

프로젝트 자바 버전 확인

  • 프로젝트가 원하는 자바 버전으로 잘 설정되어 있는지 확인해 봅니다. (Java 11)
  • File > Project Structure > Project > Project SDK
  • 버전이 맞지 않다면 변경해줍니다.

version setting

자바 직접 실행 설정

  • 빠른 실행 속도를 위해 자바로 직접 실행하도록 설정을 변경합니다.
  • Preferences > Build, Execution, Deployment > Build Tools > Gradle  

build

4. Kopring(Kotlin + Spring) Boot Project 실행

  • src > main > kotlin > com.example.kopring > KopringApplication.kt 파일을 클릭 후 실행

실행
실행 화면

5. 간단한 API 예시

  • To Do List API를 개발한다고 가정한다.
  • 조회, 등록 API 개발

1. 엔티티

  • 데이터 클래스를 쉽게 다룰 수 있도록 Kassava 의존성 추가
  • Kotlin과 Spring Data JPA를 함께 사용할 때 편의를 위해  no-arg, allopen 플러그인 추가
    • Kotlin은 기본 생성자를 자동으로 생성하지 않음
    • noArg 블록을 사용하여 JPA가 필요로 하는 엔터티 클래스의 요구 사항을 충족시키기 위해 기본 생성자를 생성합니다. 
    • allOpen 블록은 클래스에 JPA 관련 어노테이션을 추가하여 JPA에서 클래스를 올바르게 인식하도록 도와줍니다.
  • Kotlin으로 작성된 프로젝트에서 어노테이션 프로세싱(Annotation Processing)을 사용하기 위해 Kotlin Annotation Processing Tool (KAPT) 플러그인 활성화
  • 멀티 모듈 프로젝트를 위한 serialization 플러그인 추가(생략해도 됨)
  • ToDo 엔티티 작성

[build.gradle.kts]

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

plugins {
	id("org.springframework.boot") version "2.7.16"
	id("io.spring.dependency-management") version "1.0.15.RELEASE"
	kotlin("jvm") version "1.6.21"
	kotlin("plugin.spring") version "1.6.21"
	kotlin("plugin.jpa") version "1.6.21"
	kotlin("plugin.allopen") version "1.5.21" // allopen 추가
	kotlin("plugin.noarg") version "1.5.21" // noarg 추가
	kotlin("kapt") version "1.5.21" // Kotlin Annotation Processing Tool
	kotlin("plugin.serialization") version "1.5.21" // serialization (For Multi Module Project)
}

group = "com.example"
version = "0.0.1-SNAPSHOT"

java {
	sourceCompatibility = JavaVersion.VERSION_11
}

repositories {
	maven("https://plugins.gradle.org/m2/") // kassava 다운로드를 위해 추가
	mavenCentral()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-data-jpa")
	implementation("org.springframework.boot:spring-boot-starter-web")
	implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
	implementation("org.jetbrains.kotlin:kotlin-reflect")
	implementation("au.com.console:kassava:2.1.0") // kassava 추가
	runtimeOnly("com.h2database:h2")
	testImplementation("org.springframework.boot:spring-boot-starter-test")
}

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

tasks.withType<Test> {
	useJUnitPlatform()
}

allOpen {
	annotation("javax.persistence.Entity")
	annotation("javax.persistence.MappedSuperclass")
	annotation("javax.persistence.Embeddable")
}

noArg {
	annotation("javax.persistence.Entity")
	annotation("javax.persistence.MappedSuperclass")
	annotation("javax.persistence.Embeddable")
}

 

[Todo Entity]

package com.example.kopring.entity

import au.com.console.kassava.kotlinEquals
import au.com.console.kassava.kotlinHashCode
import au.com.console.kassava.kotlinToString
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id

@Entity
class Todo (
    var title: String,
    var completed: Boolean
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val id: Long? = null

    companion object {
        private val properties = arrayOf(
            Todo::title,
            Todo::completed
        )
    }

    override fun equals(other: Any?): Boolean {
        return kotlinEquals(other = other, properties = properties)
    }

    override fun hashCode(): Int {
        return kotlinHashCode(properties = properties)
    }

    override fun toString(): String {
        return kotlinToString(properties = properties)
    }
}

 

2. Repository (JPA)

package com.example.kopring.repository

import com.example.kopring.entity.Todo
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository

@Repository
interface TodoRepository : JpaRepository<Todo, Long>

 

3. Service

package com.example.kopring.service

import com.example.kopring.entity.Todo
import com.example.kopring.repository.TodoRepository
import org.springframework.stereotype.Service

@Service
class TodoService(
    private val todoRepository: TodoRepository
) {

    fun getAllTodos(): Iterable<Todo> {
        return todoRepository.findAll()
    }

    fun addTodo(title: String): Todo {
        val todo = Todo(title, false)
        return todoRepository.save(todo)
    }

}

 

4. 테스트 코드 작성

  • 테스트 코드 작성을 위해 mockk 의존성 추가

[build.gradle.kts]

testImplementation("io.mockk:mockk:1.13.4") // test를 위해 mockk 추가

 

[TodoServiceTest]

package com.example.kopring.service

import com.example.kopring.entity.Todo
import com.example.kopring.repository.TodoRepository
import io.mockk.*
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.Assertions.*

internal class TodoServiceTest {
    private lateinit var todoService: TodoService
    private lateinit var todoRepository: TodoRepository

    @BeforeEach
    fun setUp() {
        todoRepository = mockk()
        todoService = TodoService(todoRepository)
    }

    @Test
    fun getAllTodos() {
        val mockTodos = listOf(Todo( "TO DO API 개발시작", false), Todo( "TO DO API 개발완료", true))
        every { todoRepository.findAll() } returns mockTodos

        val result = todoService.getAllTodos()

        assertEquals(mockTodos, result.toList())
    }

    @Test
    fun addTodo() {
        val title = "TO DO API 배포"
        val mockTodo = Todo(title, false)
        every { todoRepository.save(any()) } returns mockTodo

        val result = todoService.addTodo(title)

        assertEquals(mockTodo, result)
    }
}

 

5. Controller

package com.example.tdd.controller

import com.example.tdd.entity.Todo
import com.example.tdd.service.TodoService
import org.springframework.web.bind.annotation.*

@RestController
@RequestMapping("/todos")
class TodoController(
    private val todoService: TodoService
    )
{
    @GetMapping("/")
    fun getAllTodos(): Iterable<Todo> {
        return todoService.getAllTodos()
    }

    @PostMapping("/")
    fun addTodo(@RequestBody title: String): Todo {
        return todoService.addTodo(title)
    }
}

 

6. API Test (Post Man )

프로젝트 실행

start

 

To Do 등록 API 

To Do 등록 API1
To Do 등록 API2

 

To Do 조회 API

To Do 조회 API