Spec-Zone.ru › Kotlin 2

Добавление поддержки баз данных в проект Spring Boot

В этой части руководства вы добавите и настроите базу данных в проекте с помощью Java Database Connectivity (JDBC). В приложениях для JVM для взаимодействия с базами данных используется JDBC. Для удобства Spring Framework предоставляет класс JdbcTemplate, который упрощает работу с JDBC и помогает избежать распространённых ошибок.

Добавление поддержки баз данных

Обычно в приложениях на основе Spring Framework логику доступа к базе данных реализуют на так называемом уровне сервисов — именно здесь находится бизнес-логика. В Spring классы следует помечать аннотацией @Service, чтобы указать, что класс относится к уровню сервисов приложения. Для этого в приложении вы создадите класс MessageService.

В том же пакете создайте файл MessageService.kt и класс MessageService, как показано ниже:

// MessageService.kt
package com.example.demo

import org.springframework.stereotype.Service
import org.springframework.jdbc.core.JdbcTemplate

@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 messages values ( ?, ? )",
            message.id, message.text
        )
        return message
    }
}
Аргумент конструктора и внедрение зависимостей — (private val db: JdbcTemplate)

У класса в Kotlin есть первичный конструктор. Также у него может быть один или несколько вторичных конструкторов. Первичный конструктор — часть заголовка класса; он располагается после имени класса и необязательных параметров типа. В нашем случае конструктор — (val db: JdbcTemplate).

val db: JdbcTemplate — это аргумент конструктора:

      @Service
      class MessageService(private val db: JdbcTemplate)
      
Замыкающее лямбда-выражение и SAM-преобразование

Функция findMessages() вызывает функцию query() класса JdbcTemplate. Функция query() принимает два аргумента: SQL-запрос в виде экземпляра String и функцию обратного вызова, которая сопоставляет один объект каждой строке:

      db.query("...", RowMapper { ... } )
      

Интерфейс RowMapper объявляет только один метод, поэтому его можно реализовать с помощью лямбда-выражения, не указывая имя интерфейса. Компилятор Kotlin знает, к какому интерфейсу нужно преобразовать лямбда-выражение, поскольку оно используется как параметр вызова функции. Это называется SAM-преобразованием в Kotlin:

      db.query("...", { ... } )
      

После SAM-преобразования у функции запроса остаются два аргумента: String на первом месте и лямбда-выражение на последнем. Согласно соглашениям Kotlin, если последний параметр функции — это функция, то лямбда-выражение, переданное как соответствующий аргумент, можно поместить за скобками. Такой синтаксис также называется замыкающим лямбда-выражением:

      db.query("...") { ... }
      
Подчёркивание для неиспользуемого аргумента лямбда-выражения

В лямбда-выражении с несколькими параметрами можно использовать символ подчёркивания _ вместо имён неиспользуемых параметров.

Итоговый синтаксис вызова функции запроса выглядит так:

      db.query("select * from messages") { response, _ ->
          Message(response.getString("id"), response.getString("text"))
      }
      

Обновление класса MessageController

Обновите MessageController.kt, чтобы использовать новый класс MessageService:

// MessageController.kt
package com.example.demo

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.GetMapping
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)
    }
}
Аннотация @PostMapping

Метод, обрабатывающий HTTP-запросы POST, необходимо пометить аннотацией @PostMapping. Чтобы преобразовать JSON, отправленный в теле HTTP-запроса, в объект, нужно использовать аннотацию @RequestBody для аргумента метода. Благодаря наличию библиотеки Jackson в classpath приложения преобразование выполняется автоматически.

ResponseEntity

ResponseEntity представляет HTTP-ответ целиком: код состояния, заголовки и тело.

С помощью метода created() можно настроить код состояния ответа (201) и задать заголовок location, указывающий путь в контексте для созданного ресурса.

Обновление класса MessageService

id для класса Message был объявлен как строка с возможным значением null:

data class Message(val id: String?, val text: String)

Однако сохранять null в базе данных как значение id неправильно: эту ситуацию нужно корректно обработать.

Обновите код файла MessageService.kt, чтобы при сохранении сообщений в базе данных генерировалось новое значение, если id имеет значение null:

// MessageService.kt
package com.example.demo

import org.springframework.stereotype.Service
import org.springframework.jdbc.core.JdbcTemplate
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 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
    }
}
Оператор Элвиса — ?:

В коде message.id ?: UUID.randomUUID().toString() используется оператор Элвиса (сокращённая запись «если не null, иначе»): ?:. Если выражение слева от ?: не равно null, оператор Элвиса возвращает его; в противном случае он возвращает выражение справа. Обратите внимание: выражение справа вычисляется только в том случае, если выражение слева имеет значение null.

Код приложения готов к работе с базой данных. Теперь необходимо настроить источник данных.

Настройка базы данных

Настройте базу данных в приложении:

  1. Создайте файл schema.sql в каталоге src/main/resources. В нём будут храниться определения объектов базы данных:

    Create database schema
  2. Обновите файл src/main/resources/schema.sql, добавив следующий код:

    -- schema.sql
    CREATE TABLE IF NOT EXISTS messages (
    id       VARCHAR(60)  PRIMARY KEY,
    text     VARCHAR      NOT NULL
    );
    

    Он создаёт таблицу messages с двумя столбцами: id и text. Структура таблицы соответствует структуре класса Message.

  3. Откройте файл application.properties в папке src/main/resources и добавьте следующие свойства приложения:

    spring.application.name=demo
    spring.datasource.driver-class-name=org.h2.Driver
    spring.datasource.url=jdbc:h2:file:./data/testdb
    spring.datasource.username=name
    spring.datasource.password=password
    spring.sql.init.schema-locations=classpath:schema.sql
    spring.sql.init.mode=always
    

    Эти параметры включают базу данных для приложения Spring Boot.
    Полный список общих свойств приложения см. в документации Spring.

Добавление сообщений в базу данных с помощью HTTP-запроса

Для работы с созданными ранее конечными точками следует использовать HTTP-клиент. В IntelliJ IDEA воспользуйтесь встроенным HTTP-клиентом:

  1. Запустите приложение. После его запуска можно выполнять POST-запросы для сохранения сообщений в базе данных.

  2. Создайте файл requests.http в корневой папке проекта и добавьте следующие HTTP-запросы:

    ### Post "Hello!"
    POST http://localhost:8080/
    Content-Type: application/json
    
    {
      "text": "Hello!"
    }
    
    ### Post "Bonjour!"
    
    POST http://localhost:8080/
    Content-Type: application/json
    
    {
      "text": "Bonjour!"
    }
    
    ### Post "Privet!"
    
    POST http://localhost:8080/
    Content-Type: application/json
    
    {
      "text": "Privet!"
    }
    
    ### Get all the messages
    GET http://localhost:8080/
    
  3. Выполните все POST-запросы. Нажмите зелёный значок Запустить на поле рядом с объявлением запроса. Эти запросы записывают текстовые сообщения в базу данных:

    Execute POST request
  4. Выполните GET-запрос и просмотрите результат в окне инструментов Запустить:

    Execute GET requests

Другой способ выполнения запросов

Можно также использовать любой другой HTTP-клиент или инструмент командной строки cURL. Например, выполните в терминале следующие команды, чтобы получить тот же результат:

curl -X POST --location "http://localhost:8080" -H "Content-Type: application/json" -d "{ \"text\": \"Hello!\" }"

curl -X POST --location "http://localhost:8080" -H "Content-Type: application/json" -d "{ \"text\": \"Bonjour!\" }"

curl -X POST --location "http://localhost:8080" -H "Content-Type: application/json" -d "{ \"text\": \"Privet!\" }"

curl -X GET --location "http://localhost:8080"

Получение сообщений по идентификатору

Расширьте функциональность приложения, чтобы получать отдельные сообщения по идентификатору.

  1. Добавьте в класс MessageService новую функцию findMessageById(id: String) для получения отдельных сообщений по идентификатору:

    // 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.*
    
    @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
        }
    }
    
    Положение аргумента vararg в списке параметров

    Функция query() принимает три аргумента:

    • Строка SQL-запроса, для выполнения которого требуется параметр

    • id — параметр типа String

    • Экземпляр RowMapper, реализуемый с помощью лямбда-выражения

    Второй параметр функции query() объявлен как параметр переменного числа аргументов (vararg). В Kotlin параметр переменного числа аргументов не обязательно должен быть последним в списке параметров.

    Функция singleOrNull()

    Функция singleOrNull() возвращает единственный элемент или null, если массив пуст или содержит более одного элемента с одинаковым значением.

    Функция .query(), используемая для получения сообщения по его идентификатору, — это функция расширения Kotlin, предоставляемая Spring Framework. Для неё требуется дополнительный импорт import org.springframework.jdbc.core.query, как показано в коде выше.

  2. Добавьте в класс MessageController новую функцию index(...) с параметром id:

    // 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() 
    }
    
    Получение значения из пути в контексте

    Сообщение id извлекается Spring Framework из пути в контексте, поскольку новая функция помечена аннотацией @GetMapping("/{id}"). Пометив аргумент функции аннотацией @PathVariable, вы указываете фреймворку использовать извлечённое значение в качестве аргумента функции. Новая функция вызывает MessageService для получения отдельного сообщения по его идентификатору.

    Функция расширения с nullable-приёмником

    Функции расширения можно определять с типом приёмника, допускающим значение null. Если приёмник равен null, то this также равно null. Поэтому при определении расширения с типом приёмника, допускающим значение null, рекомендуется выполнять проверку this == null в теле функции.

    Для проверки на null также можно использовать оператор безопасного вызова (?.), как в приведённой выше функции toResponseEntity():

          this?.let { ResponseEntity.ok(it) }
          
    ResponseEntity

    ResponseEntity представляет HTTP-ответ, включая код состояния, заголовки и тело. Это универсальная оболочка, которая позволяет отправлять клиенту настраиваемые HTTP-ответы и более гибко управлять их содержимым.

Полный код приложения:

// 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 data class Message(val id: String?, val text: String)
// 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.* @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() db.update( "insert into messages values ( ?, ? )", id, message.text ) return message.copy(id = id) } }
// 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> = this?.let { ResponseEntity.ok(it) } ?: ResponseEntity.notFound().build() }

Запуск приложения

Приложение Spring готово к запуску:

  1. Запустите приложение ещё раз.

  2. Откройте файл requests.http и добавьте новый GET-запрос:

    ### Get the message by its id
    GET http://localhost:8080/id
    
  3. Выполните GET-запрос, чтобы получить все сообщения из базы данных.

  4. В окне инструментов Запустить скопируйте один из идентификаторов и добавьте его в запрос, как показано ниже:

    ### Get the message by its id
    GET http://localhost:8080/f910aa7e-11ee-4215-93ed-1aeeac822707
    

    Вместо приведённого выше укажите идентификатор своего сообщения.

  5. Выполните GET-запрос и просмотрите результат в окне инструментов Запустить:

    Retrieve message by its id

Следующий шаг

На последнем шаге вы узнаете, как использовать более популярный способ подключения к базе данных с помощью Spring Data.

  • Предыдущий шаг

  • Следующий шаг

01 сентября 2026
Добавление класса данных в проект Spring BootИспользование Spring Data CrudRepository для доступа к базе данных

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/jvm-spring-boot-add-db-support.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API