From 1865360e832733733e3ffce886a25a6ff0a7002e Mon Sep 17 00:00:00 2001 From: Andrey Polyakov Date: Wed, 2 Aug 2023 15:32:43 +0200 Subject: [PATCH] update: update the Spring Boot tutorial (#3721) * update: add note about IntelliJ IDEA version KT-60838 * update: update the Spring Boot tutorial * fix: fix codeblocks * Update docs/topics/jvm/jvm-create-project-with-spring-boot.md * Update docs/topics/jvm/jvm-create-project-with-spring-boot.md * Apply suggestions from code review Co-authored-by: Sarah Haggarty <81160244+sarahhaggarty@users.noreply.github.com> --------- Co-authored-by: Sarah Haggarty <81160244+sarahhaggarty@users.noreply.github.com> --- .../jvm-create-project-with-spring-boot.md | 23 ++++-- .../jvm/jvm-spring-boot-add-data-class.md | 2 +- .../jvm/jvm-spring-boot-add-db-support.md | 66 ++++++++++++++- .../jvm-spring-boot-using-crudrepository.md | 81 +++++++++++++++++-- 4 files changed, 156 insertions(+), 16 deletions(-) diff --git a/docs/topics/jvm/jvm-create-project-with-spring-boot.md b/docs/topics/jvm/jvm-create-project-with-spring-boot.md index 82309ee1a36..de295c14621 100644 --- a/docs/topics/jvm/jvm-create-project-with-spring-boot.md +++ b/docs/topics/jvm/jvm-create-project-with-spring-boot.md @@ -12,6 +12,10 @@ The first part of the tutorial shows you how to create a Spring Boot project in Download and install the latest version of [IntelliJ IDEA Ultimate Edition](https://www.jetbrains.com/idea/download/index.html). +> If you use IntelliJ IDEA Community Edition or another IDE, you can generate a Spring Boot project using a [web-based project generator](https://start.spring.io). +> +{type="note"} + ## Create a Spring Boot project Create a new Spring Boot project with Kotlin by using the Project Wizard in IntelliJ IDEA Ultimate Edition: @@ -49,7 +53,9 @@ Create a new Spring Boot project with Kotlin by using the Project Wizard in Inte 6. Click **Create** to generate and set up the project. - The IDE will generate and open the new project. It may take some time to download and import the project dependencies. + > The IDE will generate and open a new project. It may take some time to download and import the project dependencies. + > + {type="tip"} 7. After this, you can observe the following structure in the **Project view**: @@ -71,15 +77,18 @@ Here is the full script with the explanation of all parts and dependencies: import org.jetbrains.kotlin.gradle.tasks.KotlinCompile // For `KotlinCompile` task below plugins { - id("org.springframework.boot") version "2.7.1" - id("io.spring.dependency-management") version "1.0.11.RELEASE" + id("org.springframework.boot") version "3.1.2" + id("io.spring.dependency-management") version "1.1.2" kotlin("jvm") version "%kotlinVersion%" // The version of Kotlin to use kotlin("plugin.spring") version "%kotlinVersion%" // The Kotlin Spring plugin } group = "com.example" -version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_17 +version = "0.0.1-SNAPSHOT" + +java { + sourceCompatibility = JavaVersion.VERSION_17 +} repositories { mavenCentral() @@ -126,7 +135,7 @@ As you can see, there are a few Kotlin-related artifacts added to the Gradle bui Open the `DemoApplication.kt` file: ```kotlin -package demo +package com.example.demo import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @@ -224,7 +233,7 @@ class MessageController { Here is a complete code of the `DemoApplication.kt`: ```kotlin -package demo +package com.example.demo import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/docs/topics/jvm/jvm-spring-boot-add-data-class.md b/docs/topics/jvm/jvm-spring-boot-add-data-class.md index b98a6dfdcfd..818dd7deb90 100644 --- a/docs/topics/jvm/jvm-spring-boot-add-data-class.md +++ b/docs/topics/jvm/jvm-spring-boot-add-data-class.md @@ -98,7 +98,7 @@ The response from `MessageController` will now be a JSON document containing a c Here is a complete code of the `DemoApplication.kt`: ```kotlin -package demo +package com.example.demo import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication diff --git a/docs/topics/jvm/jvm-spring-boot-add-db-support.md b/docs/topics/jvm/jvm-spring-boot-add-db-support.md index f24bc6252a6..c42d2d9d07b 100644 --- a/docs/topics/jvm/jvm-spring-boot-add-db-support.md +++ b/docs/topics/jvm/jvm-spring-boot-add-db-support.md @@ -36,7 +36,7 @@ class MessageService(val db: JdbcTemplate) { -

A class in Kotlin can have a primary constructor and one or more secondary constructors. +

A class in Kotlin has a primary constructor. It can also have one or more secondary constructors. The primary constructor is a part of the class header, and it goes after the class name and optional type parameters. In our case, the constructor is (val db: JdbcTemplate).

val db: JdbcTemplate is the constructor's argument:

@@ -171,7 +171,7 @@ Configure the database in the application: You should use an HTTP client to work with previously created endpoints. In IntelliJ IDEA, use the embedded HTTP client: 1. Run the application. Once the application is up and running, you can execute POST requests to store messages in the database. - Create the `requests.http` file and add the following HTTP requests: + Create the `requests.http` file in the `src/main/resources` folder and add the following HTTP requests: ```http request ### Post "Hello!" @@ -256,13 +256,15 @@ Extend the functionality of the application to retrieve the individual messages } ``` - > The `query()` function that is used to fetch the message by its id is a [Kotlin extension function](extensions.md#extension-functions) provided by the Spring Framework and requires an additional import as in the code above. + > The `.query()` function that is used to fetch the message by its id is a [Kotlin extension function](extensions.md#extension-functions) provided by the Spring Framework and requires an additional import as in the code above. > {type="note"} 2. Add the new `index(...)` function with the `id` parameter to the `MessageController` class: ```kotlin + import org.springframework.web.bind.annotation.* + @RestController class MessageController(val service: MessageService) { @GetMapping("/") @@ -294,6 +296,64 @@ Extend the functionality of the application to retrieve the individual messages
+Here is a complete code of the `DemoApplication.kt`: + +```kotlin +package com.example.demo + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.stereotype.Service +import org.springframework.jdbc.core.JdbcTemplate +import java.util.UUID +import org.springframework.jdbc.core.query +import org.springframework.web.bind.annotation.* + + +@SpringBootApplication +class DemoApplication + +fun main(args: Array) { + runApplication(*args) +} + +@RestController +class MessageController(val service: MessageService) { + @GetMapping("/") + fun index(): List = service.findMessages() + + @GetMapping("/{id}") + fun index(@PathVariable id: String): List = + service.findMessageById(id) + + @PostMapping("/") + fun post(@RequestBody message: Message) { + service.save(message) + } +} + +data class Message(val id: String?, val text: String) + +@Service +class MessageService(val db: JdbcTemplate) { + + fun findMessages(): List = db.query("select * from messages") { response, _ -> + Message(response.getString("id"), response.getString("text")) + } + + fun findMessageById(id: String): List = db.query("select * from messages where id = ?", id) { response, _ -> + Message(response.getString("id"), response.getString("text")) + } + + fun save(message: Message) { + val id = message.id ?: UUID.randomUUID().toString() + db.update("insert into messages values ( ?, ? )", + id, message.text) + } +} +``` +{initial-collapse-state="collapsed"} + ## Run the application The Spring application is ready to run: diff --git a/docs/topics/jvm/jvm-spring-boot-using-crudrepository.md b/docs/topics/jvm/jvm-spring-boot-using-crudrepository.md index 63d1126699d..6ff5c8a1153 100644 --- a/docs/topics/jvm/jvm-spring-boot-using-crudrepository.md +++ b/docs/topics/jvm/jvm-spring-boot-using-crudrepository.md @@ -42,6 +42,8 @@ First, you need to adjust the `Message` class for work with the `CrudRepository` 3. Update the `MessageService` class. It will now call to the `MessageRepository` instead of executing SQL queries: ```kotlin + import java.util.* + @Service class MessageService(val db: MessageRepository) { fun findMessages(): List = db.findAll().toList() @@ -60,7 +62,7 @@ First, you need to adjust the `Message` class for work with the `CrudRepository`

The return type of the findById() function in the CrudRepository interface is an instance of the Optional class. However, it would be convenient to return a List with a single message for consistency. For that, you need to unwrap the Optional value if it’s present, and return a list with the value. This can be implemented as an extension function to the Optional type.

-

In the code, Optional<out T>.toList(), toList() is the extension function for Optional. Extension functions allow you to write additional functions to any classes, which is especially useful when you want to extend functionality of some library class.

+

In the code, Optional<out T>.toList(), .toList() is the extension function for Optional. Extension functions allow you to write additional functions to any classes, which is especially useful when you want to extend functionality of some library class.

This function works with an assumption that the new object doesn’t have an id in the database. Hence, the id should be null for insertion.

@@ -72,12 +74,81 @@ First, you need to adjust the `Message` class for work with the `CrudRepository` 4. Update the messages table definition to generate the ids for the inserted objects. Since `id` is a string, you can use the `RANDOM_UUID()` function to generate the id value by default: ```sql - CREATE TABLE messages ( - id VARCHAR(60) DEFAULT RANDOM_UUID() PRIMARY KEY, - text VARCHAR NOT NULL - ); + CREATE TABLE IF NOT EXISTS messages ( + id VARCHAR(60) DEFAULT RANDOM_UUID() PRIMARY KEY, + text VARCHAR NOT NULL + ); ``` +5. Update the name of the database in the `application.properties` file located in the `src/main/resources` folder: + + ```none + 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 + ``` + +Here is the complete code for `DemoApplication.kt`: + +```kotlin +package com.example.demo + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import org.springframework.data.repository.CrudRepository +import org.springframework.stereotype.Service +import org.springframework.web.bind.annotation.* +import java.util.* + + +@SpringBootApplication +class DemoApplication + +fun main(args: Array) { + runApplication(*args) +} + +@RestController +class MessageController(val service: MessageService) { + @GetMapping("/") + fun index(): List = service.findMessages() + + @GetMapping("/{id}") + fun index(@PathVariable id: String): List = + service.findMessageById(id) + + @PostMapping("/") + fun post(@RequestBody message: Message) { + service.save(message) + } +} + +interface MessageRepository : CrudRepository + +@Table("MESSAGES") +data class Message(@Id var id: String?, val text: String) + +@Service +class MessageService(val db: MessageRepository) { + fun findMessages(): List = db.findAll().toList() + + fun findMessageById(id: String): List = db.findById(id).toList() + + fun save(message: Message) { + db.save(message) + } + + fun Optional.toList(): List = + if (isPresent) listOf(get()) else emptyList() +} +``` +{initial-collapse-state="collapsed"} + ## Run the application The application is ready to run again.