Kotlin + JPA로 CRUD REST API 빠르게 만들기 – 실전 코드 중심 가이드
이 글은 이론 설명을 최소화하고, 실제로 바로 사용할 수 있는 Kotlin + Spring Boot + JPA 기반 CRUD REST API 코드만을 집중적으로 제공합니다.
목차
- build.gradle.kts – 의존성
- Entity – 데이터 모델
- Repository – JPA 인터페이스
- DTO – 요청/응답 객체
- Service – 비즈니스 로직
- Controller – REST API
- 예외 처리 – 글로벌 핸들러
- 테스트 코드 – 통합 테스트
- application.yml – 환경설정
1. build.gradle.kts – 의존성
설명: 이 파일은 Spring Boot, JPA, Kotlin, H2 데이터베이스 등 프로젝트에 필요한 핵심 라이브러리(의존성)를 정의합니다.
implementation
은 실제 코드에서 사용하는 라이브러리,runtimeOnly
는 실행 시 필요한 라이브러리,testImplementation
은 테스트용 라이브러리입니다. H2는 개발/테스트용 인메모리 DB로 빠른 실습이 가능합니다. 이 설정만으로 Spring Boot 기반 REST API 프로젝트의 기본 구성이 완료됩니다.
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
runtimeOnly("com.h2database:h2") // 개발용 인메모리 DB
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
2. Entity – 데이터 모델
설명: Entity 클래스는 DB 테이블과 1:1로 매핑되는 데이터 구조입니다. 여기서는
Post
라는 이름의 게시글 엔티티를 정의하며,@Entity
와@Table
어노테이션으로 JPA가 이 클래스를 테이블로 인식하게 합니다.id
는 기본키(PK)로 자동 증가,title
과content
는 게시글의 제목과 내용을 저장합니다. 이 구조만으로 DB 테이블 생성 및 데이터 저장이 자동화됩니다.
import jakarta.persistence.*
@Entity
@Table(name = "posts")
data class Post(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,
var title: String,
var content: String
)
3. Repository – JPA 인터페이스
설명: Repository는 DB와 직접적으로 데이터를 주고받는 계층입니다.
JpaRepository
를 상속하면, 별도의 쿼리 없이도 CRUD(생성, 조회, 수정, 삭제) 기능을 모두 사용할 수 있습니다. 즉,PostRepository
만 선언해도 게시글 저장, 조회, 삭제 등의 DB 작업이 자동으로 구현됩니다.
import org.springframework.data.jpa.repository.JpaRepository
interface PostRepository : JpaRepository<Post, Long>
4. DTO – 요청/응답 객체
설명: DTO(Data Transfer Object)는 API 요청/응답에서 사용하는 데이터 구조입니다. 클라이언트가 게시글을 생성/수정할 때는
PostRequest
를, 서버가 결과를 반환할 때는PostResponse
를 사용합니다. 엔티티와 분리해 API 보안과 유연성을 높이고, 불필요한 정보 노출을 막을 수 있습니다.
data class PostRequest(
val title: String,
val content: String
)
data class PostResponse(
val id: Long,
val title: String,
val content: String
)
5. Service – 비즈니스 로직
설명: Service 계층은 비즈니스 로직(실제 데이터 처리, 트랜잭션 관리 등)을 담당합니다. Controller에서 받은 요청을 처리하고, Repository를 통해 DB와 통신합니다.
@Transactional
로 트랜잭션을 관리하여 데이터 일관성을 보장합니다. CRUD 각 메서드는 요청을 받아 저장/조회/수정/삭제 후 DTO로 결과를 반환합니다.
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
@Service
class PostService(
private val postRepository: PostRepository
) {
@Transactional
fun createPost(request: PostRequest): PostResponse {
val post = postRepository.save(Post(title = request.title, content = request.content))
return PostResponse(post.id, post.title, post.content)
}
@Transactional(readOnly = true)
fun getPost(id: Long): PostResponse =
postRepository.findById(id)
.map { PostResponse(it.id, it.title, it.content) }
.orElseThrow { NoSuchElementException("Post not found") }
@Transactional(readOnly = true)
fun getAllPosts(): List<PostResponse> =
postRepository.findAll().map { PostResponse(it.id, it.title, it.content) }
@Transactional
fun updatePost(id: Long, request: PostRequest): PostResponse {
val post = postRepository.findById(id).orElseThrow { NoSuchElementException("Post not found") }
post.title = request.title
post.content = request.content
return PostResponse(post.id, post.title, post.content)
}
@Transactional
fun deletePost(id: Long) {
postRepository.deleteById(id)
}
}
6. Controller – REST API
설명: Controller는 실제로 HTTP 요청을 받아 처리하는 계층입니다. 각 메서드는 URL, HTTP 메서드(POST/GET/PUT/DELETE)에 따라 Service 계층을 호출하고, 결과를 반환합니다.
@RestController
와@RequestMapping
을 활용해 RESTful API 엔드포인트를 쉽게 만들 수 있습니다. 이 구조로 클라이언트와 서버가 데이터를 주고받을 수 있습니다.
import org.springframework.web.bind.annotation.*
@RestController
@RequestMapping("/api/posts")
class PostController(
private val postService: PostService
) {
@PostMapping
fun create(@RequestBody request: PostRequest) = postService.createPost(request)
@GetMapping("/{id}")
fun get(@PathVariable id: Long) = postService.getPost(id)
@GetMapping
fun getAll() = postService.getAllPosts()
@PutMapping("/{id}")
fun update(@PathVariable id: Long, @RequestBody request: PostRequest) = postService.updatePost(id, request)
@DeleteMapping("/{id}")
fun delete(@PathVariable id: Long) = postService.deletePost(id)
}
7. 예외 처리 – 글로벌 핸들러
설명: 글로벌 예외처리기는 API 전체에서 발생하는 예외를 한 곳에서 처리합니다.
@RestControllerAdvice
와@ExceptionHandler
를 사용하면, 예외 발생 시 사용자에게 일관된 에러 메시지와 HTTP 상태코드를 반환할 수 있습니다. 예를 들어, 없는 게시글을 조회하면 404 NOT FOUND 에러와 메시지를 자동으로 응답합니다.
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
@RestControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(NoSuchElementException::class)
fun handleNotFound(e: NoSuchElementException) =
mapOf("error" to e.message, "status" to HttpStatus.NOT_FOUND.value())
}
8. 테스트 코드 – 통합 테스트
설명: 통합 테스트는 실제 API가 제대로 동작하는지 자동으로 검증합니다. MockMvc를 사용해 HTTP 요청을 보내고, 응답값과 상태코드를 확인합니다. 게시글 생성, 조회, 수정, 삭제까지 전체 흐름을 테스트해, 코드 변경 시에도 API가 정상 동작하는지 쉽게 확인할 수 있습니다.
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.post
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.put
import org.springframework.test.web.servlet.delete
import com.fasterxml.jackson.databind.ObjectMapper
@SpringBootTest
@AutoConfigureMockMvc
class PostControllerTest @Autowired constructor(
val mockMvc: MockMvc,
val objectMapper: ObjectMapper
) {
@Test
fun `POST, GET, PUT, DELETE API 통합 테스트`() {
// Create
val req = PostRequest("title", "content")
val createRes = mockMvc.post("/api/posts") {
contentType = MediaType.APPLICATION_JSON
content = objectMapper.writeValueAsString(req)
}.andExpect {
status { isOk() }
jsonPath("$.title") { value("title") }
}.andReturn()
val id = objectMapper.readTree(createRes.response.contentAsString).get("id").asLong()
// Get
mockMvc.get("/api/posts/$id")
.andExpect { status { isOk() } }
// Update
val updateReq = PostRequest("updated", "updated content")
mockMvc.put("/api/posts/$id") {
contentType = MediaType.APPLICATION_JSON
content = objectMapper.writeValueAsString(updateReq)
}.andExpect {
status { isOk() }
jsonPath("$.title") { value("updated") }
}
// Delete
mockMvc.delete("/api/posts/$id")
.andExpect { status { isOk() } }
}
}
9. application.yml – 환경설정
설명: Spring Boot 프로젝트의 환경설정을 담당하는 파일입니다. H2 인메모리 데이터베이스, JPA 자동 테이블 생성, SQL 로그 출력 등 개발에 필요한 설정이 모두 포함되어 있습니다. 이 설정만으로 별도 DB 설치 없이 바로 실습이 가능하며, 개발/테스트 환경에서 빠른 피드백을 받을 수 있습니다.
spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
hibernate:
ddl-auto: update
show-sql: true
properties:
hibernate:
format_sql: true
h2:
console:
enabled: true
이상으로, 이론 설명 없이 바로 실무에 복붙 가능한 Kotlin + JPA 기반 CRUD REST API 전체 코드 예시를 제공합니다. 궁금한 점이나 추가 실전 예제가 필요하면 언제든 요청해 주세요!