使用 Spring Data CrudRepository 进行数据库访问

This is the final part of the Getting started with Spring Boot and Kotlin tutorial. Before proceeding, make sure you've completed previous steps:


First step Create a Spring Boot project with Kotlin
Second step Add a data class to the Spring Boot project
Third step Add database support for Spring Boot project
Fourth step Use Spring Data CrudRepository for database access

In this part, you will migrate the service layer to use the Spring Data CrudRepository instead of JdbcTemplate for database access. CrudRepository is a Spring Data interface for generic CRUD operations on a repository of a specific type. It provides several methods out of the box for interacting with a database.

Update your application

First, you need to adjust the Message class for work with the CrudRepository API:

  1. Add the @Table annotation to the Message class to declare mapping to a database table.
    Add the @Id annotation before the id field.

    These annotations also require additional imports.

    {style="note"}

     // Message.kt
     package demo
    
     import org.springframework.data.annotation.Id
     import org.springframework.data.relational.core.mapping.Table
    
     @Table("MESSAGES")
     data class Message(@Id val id: String?, val text: String)
    

    In addition, to make the use of the Message class more idiomatic, you can set the default value for id property to null and flip the order of the data class properties:

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

    Now if you need to create a new instance of the Message class, you can only specify the text property as a parameter:

     val message = Message("Hello") // id is null
    
  2. Declare an interface for the CrudRepository that will work with the Message data class. Create the MessageRepository.kt file and add the following code to it:

     // MessageRepository.kt
     package demo
    
     import org.springframework.data.repository.CrudRepository
    
     interface MessageRepository : CrudRepository
    
  3. Update the MessageService class. It will now use the MessageRepository instead of executing SQL queries:

     // MessageService.kt
     package demo
    
     import org.springframework.data.repository.findByIdOrNull
     import org.springframework.stereotype.Service
    
     @Service
     class MessageService(private val db: MessageRepository) {
         fun findMessages(): List = db.findAll().toList()
    
         fun findMessageById(id: String): Message? = db.findByIdOrNull(id)
    
         fun save(message: Message): Message = db.save(message)
     }
    

    The findByIdOrNull() function is an extension function for CrudRepository interface in Spring Data JDBC.

    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.

    If the id isn't null, CrudRepository assumes that the object already exists in the database and this is an update operation as opposed to an insert operation. After the insert operation, the id will be generated by the data store and assigned back to the Message instance. This is why the id property should be declared using the var keyword.

  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:

     -- schema.sql 
     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:

    spring.application.name=demo
    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 of the application:

// DemoApplication.kt
package demo

import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication

@SpringBootApplication
class DemoApplication

fun main(args: Array) {
    runApplication(*args)
}
// Message.kt
package demo

import org.springframework.data.annotation.Id
import org.springframework.data.relational.core.mapping.Table

@Table("MESSAGES")
data class Message(val text: String, @Id val id: String? = null)
// MessageRepository.kt
package demo

import org.springframework.data.repository.CrudRepository

interface MessageRepository : CrudRepository
// MessageService.kt
package demo

import org.springframework.data.repository.findByIdOrNull
import org.springframework.stereotype.Service

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

    fun findMessageById(id: String): Message? = db.findByIdOrNull(id)

    fun save(message: Message): Message = db.save(message)
}
// MessageController.kt
package 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 {
        val savedMessage = service.save(message)
        return ResponseEntity.created(URI("/${savedMessage.id}")).body(savedMessage)
    }

    @GetMapping("/{id}")
    fun getMessage(@PathVariable id: String): ResponseEntity =
        service.findMessageById(id).toResponseEntity()

    private fun Message?.toResponseEntity(): ResponseEntity =
        // If the message is null (not found), set response code to 404
        this?.let { ResponseEntity.ok(it) } ?: ResponseEntity.notFound().build()
}

Run the application

The application is ready to run again. By replacing the JdbcTemplate with CrudRepository, the functionality didn't change hence the application should work the same way as previously.

What's next

Get your personal language map to help you navigate Kotlin features and track your progress in studying the language:

Get the Kotlin language map