Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update: update the Spring Boot tutorial #3721

Merged
merged 6 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 17 additions & 8 deletions docs/topics/jvm/jvm-create-project-with-spring-boot.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 or other IDE, you can generate a Spring Boot project using a [web-based project generator](https://start.spring.io).
koshachy marked this conversation as resolved.
Show resolved Hide resolved
>
{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:
Expand Down Expand Up @@ -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 the new project. It may take some time to download and import the project dependencies.
koshachy marked this conversation as resolved.
Show resolved Hide resolved
>
{type="tip"}

7. After this, you can observe the following structure in the **Project view**:

Expand All @@ -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
koshachy marked this conversation as resolved.
Show resolved Hide resolved
}

repositories {
mavenCentral()
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -236,7 +245,7 @@ import org.springframework.web.bind.annotation.RestController
class DemoApplication

fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
runApplication<DemoApplication>(*args)
koshachy marked this conversation as resolved.
Show resolved Hide resolved
}

@RestController
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/jvm/jvm-spring-boot-add-data-class.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 63 additions & 3 deletions docs/topics/jvm/jvm-spring-boot-add-db-support.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class MessageService(val db: JdbcTemplate) {

<deflist collapsible="true">
<def title="Constructor argument and dependency injection – (val db: JdbcTemplate)">
<p>A class in Kotlin can have a primary constructor and one or more <a href="classes.md#secondary-constructors">secondary constructors</a>.
<p>A class in Kotlin has a primary constructor. It also can have one or more <a href="classes.md#secondary-constructors">secondary constructors</a>.
koshachy marked this conversation as resolved.
Show resolved Hide resolved
The <i>primary constructor</i> is a part of the class header, and it goes after the class name and optional type parameters. In our case, the constructor is <code>(val db: JdbcTemplate)</code>.</p>
<p><code>val db: JdbcTemplate</code> is the constructor's argument:</p>
<code style="block" lang="kotlin">
Expand Down Expand Up @@ -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!"
Expand Down Expand Up @@ -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("/")
Expand Down Expand Up @@ -294,6 +296,64 @@ Extend the functionality of the application to retrieve the individual messages
</def>
</deflist>

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<String>) {
runApplication<DemoApplication>(*args)
}

@RestController
class MessageController(val service: MessageService) {
@GetMapping("/")
fun index(): List<Message> = service.findMessages()

@GetMapping("/{id}")
fun index(@PathVariable id: String): List<Message> =
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<Message> = db.query("select * from messages") { response, _ ->
Message(response.getString("id"), response.getString("text"))
}

fun findMessageById(id: String): List<Message> = 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:
Expand Down
81 changes: 76 additions & 5 deletions docs/topics/jvm/jvm-spring-boot-using-crudrepository.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<Message> = db.findAll().toList()
Expand All @@ -60,7 +62,7 @@ First, you need to adjust the `Message` class for work with the `CrudRepository`
<deflist collapsible="true">
<def title="Extension functions">
<p>The return type of the <code>findById()</code> function in the <code>CrudRepository</code> interface is an instance of the <code>Optional</code> class. However, it would be convenient to return a <code>List</code> with a single message for consistency. For that, you need to unwrap the <code>Optional</code> value if it’s present, and return a list with the value. This can be implemented as an <a href="extensions.md#extension-functions">extension function</a> to the <code>Optional</code> type.</p>
<p>In the code, <code>Optional&lt;out T&gt;.toList()</code>, <code>toList()</code> is the extension function for <code>Optional</code>. 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.</p>
<p>In the code, <code>Optional&lt;out T&gt;.toList()</code>, <code>.toList()</code> is the extension function for <code>Optional</code>. 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.</p>
</def>
<def title="CrudRepository save() function">
<p><a href="https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#jdbc.entity-persistence">This function works</a> with an assumption that the new object doesn’t have an id in the database. Hence, the id <b>should be null</b> for insertion.</p>
Expand All @@ -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 a complete code of the `DemoApplication.kt`:
koshachy marked this conversation as resolved.
Show resolved Hide resolved

```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<String>) {
runApplication<DemoApplication>(*args)
}

@RestController
class MessageController(val service: MessageService) {
@GetMapping("/")
fun index(): List<Message> = service.findMessages()

@GetMapping("/{id}")
fun index(@PathVariable id: String): List<Message> =
service.findMessageById(id)

@PostMapping("/")
fun post(@RequestBody message: Message) {
service.save(message)
}
}

interface MessageRepository : CrudRepository<Message, String>

@Table("MESSAGES")
data class Message(@Id var id: String?, val text: String)

@Service
class MessageService(val db: MessageRepository) {
fun findMessages(): List<Message> = db.findAll().toList()

fun findMessageById(id: String): List<Message> = db.findById(id).toList()

fun save(message: Message) {
db.save(message)
}

fun <T : Any> Optional<out T>.toList(): List<T> =
if (isPresent) listOf(get()) else emptyList()
}
```
{initial-collapse-state="collapsed"}

## Run the application

The application is ready to run again.
Expand Down
Loading