해당 파트에서는 Service layer를 작성한다. 따라서 앞선 파일들과 같은 패키지에 MessageService.kt 파일을 아래와 같이 작성해주었다.
// MessageService.kt
package com.example.demo
import org.springframework.steretype.Service
import org.springframework.jdbc.core.JdbcTemplate
import java.util.*
@Service
class MessageService(private val db: JdbcTemplate) {
fun findMessages() : List<Message> = db.query("select * from messages") { response, _ ->
Message(response.getString("id"), response.getString("text"))
}
fun save(message: Message): Message {
db.update(
"insert into message values ( ?, ? )",
message.id, message.text
)
return message
}
}
위 코드에서 findMessages() 함수는 message 테이블에서 모든 레코드를 조회하여 Message 객체의 리스트를 반환한다. 이 함수 내에서 db.query() 함수는 db.query(". . .", RowMapper { . . . } )의 형태를 가지고 있는데, RowMapper 인터페이스가 단 하나의 추상메서드를 가지고 있으므로, 이를 람다식으로 변환한 SAM 변환이 적용된 예시라고 볼 수 있다. SAM 변환에 대한 자세한 내용은 아래 링크를 참고하면 된다. 또한, 마지막 parameter가 함수일 때 해당 parameter로 전달된 람다 표현식을 괄호 밖으로 뺄 수 있는 Tariling Lambda 문법이 함께 사용되었다.
https://duft-doft.tistory.com/57
save() 함수는 message 객체를 데이터베이스에 저장한 후 저장한 객체를 반환한다. 쿼리 동작은 다음과 같다.
SQL: "insert into message values ( ?, ? )" — 메시지를 삽입.
- message.id: 첫 번째 ?에 매핑되는 값.
- message.text: 두 번째 ?에 매핑되는 값.
이에 맞춰 MessageController도 수정한다. @GetMapping annotation이 붙은 listMessage 함수는 HTTP Get 요청(GET http://localhost:8080//)을 수행하고, @PostMapping annotation이 붙은 post 함수는 HTTP Post 요청 (POST http://localhost:8080/)를 수행한다. Get과 Post 요청의 차이점은 아래 링크에서 읽어볼 수 있다. 간단히 말하면, Get 요청은 서버에서 정보를 검색하는 데 주로 사용되고, Post 요청은 일부를 업데이트 및 삭제하는데 일반적으로 사용된다.
https://apidog.com/kr/blog/get-vs-post-request-the-difference-between-http-methods-2/
// MessageController.kt
package com.example.demo
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.net.URI
@RestController
@RequestMapping("/")
class MessageController(private val service: MessageService) {
@GetMapping
fun listMessages() = service.findMessage()
@PostMapping
fun post(@RequestBody message: Message): ResponseEntity<Message> {
val savedMessage = service.save(message)
return ResponseEntity.created(URI("/${savedMessage.id}")).body(savedMessage)
}
}
더 자세히 각 함수를 살펴보면, listMessage() 함수는 서비스 계층의 findMessage 메서드를 호출하여 메시지 목록을 반환하고, post() 함수는 요청의 본문(@RequestBody)을 parameter를 통해 Message 객체로 매핑한다. 이를 서비스 계층의 save 메소드로 데이터 베이스에 저장한다. 마지막으로 HTTP 응답 상태 코드를 201 Created로 설정(ResponseEntity.created()) 하여 이 상태와 저장된 메시지 객체를 반환한다.
여기서 ResponseEntity는 전체 HTTP response(status code, headers, body)를 담고 있는 스프링 클래스이다. 해당 코드에서는 created() 메소드를 이용해 status code를 201 Created 로 설정한 것이다.
다음은 id parameter가 nullable String이라는 사실을 이용해서 코드를 수정한다. save 함수만 수정한 것인데, 우선 id라는 변수를 하나 추가하여, message.id가 null이 아니면 그대로 넣고, null 이면 UUID를 생성하여 고유한 id로 사용할 수 있도록 Elvis operator를 이용하여 대입한다. 나머지 과정은 모두 같고, 마지막에 return값을 message를 모두 그대로 복사하되, id 필드만 새로 설정한 값인 id를 대입하여 변경한 값을 반환한다.
여기에 더하여 Message Service class 에 개별 메시지에 id를 통해 접근할 수 있도록 findMessageById 함수를 추가한다.
// MessageService.kt
package com.example.demo
import org.springframework.stereotype.Service
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.jdbc.core.query
import java.util.UUID
@Service
class MessageService(private val db: JdbcTemplate) {
fun findMessages(): List<Message> = db.query("select * from messages") { response, _ ->
Message(response.getString("id"), response.getString("text"))
}
fun findMessageById(id: String): Message? = db.query("select * from messages where id = ?", id) { response, _ ->
Message(response.getString("id"), response.getString("text"))
}.singleOrNull()
fun save(message: Message): Message {
val id = message.id ?: UUID.randomUUID().toString() // Generate new id if it is null
db.update(
"insert into messages values ( ?, ? )",
id, message.text
)
return message.copy(id = id) // Return a copy of the message with the new id
}
}
위에서 더한 findMessageById function을 활용할 수 있도록 MessageController에도 id parameter를 추가해준다.
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() = 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()
}
우선 mapping을 할 때의 컨텍스트 경로에서 id값을 가져올 수 있다. getMessage 함수의 parameter로 @PathVariable annotation을 통해서 컨텍스트 경로 내 id를 id 파라미터에 매핑한다. 이 id를 이용해 서비스 개체의 findMessageById 메서드를 활용해 메시지를 찾은 후 이를 ResponseEntity로 만들기 위한 추가 작업을 toResponseEntity() 함수를 통해 수행한다.
toResponseEntity 함수에서 우선 알아야 할 점은 안전한 호출 연산자(?.)인데, 이는 메소드 호출 시 점 대신 ?. 연산자를 사용하면 null 값이 아닌 경우에만 메서드를 호출해주는 연산자이다. 이 연산자를 이용하여 message가 null이 아닌 경우에는 200 ok를 반환하고, 만약 null값인 경우 404 Not Found를 반환하게 된다.
'2024-겨울 프로젝트 관련 공부' 카테고리의 다른 글
[Spring Boot] [기본 개념] MVC, DI 공부 (0) | 2025.01.07 |
---|---|
[Spring Boot / Kotlin] 튜토리얼4, Spring Data CrudRepository 활용하기 (0) | 2025.01.07 |
[Spring Boot / Kotlin] 튜토리얼2, data class 추가하기 (0) | 2025.01.04 |
[Spring Boot / Kotlin] 튜토리얼1, Spring Boot 시작하기 (0) | 2025.01.04 |
[웹서비스 프로젝트/백엔드] Kotlin 기본 공부 (2) | 2024.12.26 |