Skip to content

Commit

Permalink
update: update the Spring Boot tutorial (#3721)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

---------

Co-authored-by: Sarah Haggarty <[email protected]>
  • Loading branch information
koshachy and sarahhaggarty authored Aug 2, 2023
1 parent 6fa0bfe commit 1865360
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 16 deletions.
23 changes: 16 additions & 7 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 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:
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 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**:

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
}

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 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 can also have one or more <a href="classes.md#secondary-constructors">secondary constructors</a>.
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 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<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

0 comments on commit 1865360

Please sign in to comment.