6.1.7.5 使用 Spring Data CrudRepository 访问数据库

原文链接: https://kotlinlang.org/docs/jvm-spring-boot-using-crudrepository.html

6.1.7.5 使用 Spring Data CrudRepository 访问数据库

在用 Kotlin 编写的 Spring Boot 项目中使用 Spring Data 接口。

在这一部分,你将把服务层从使用 JdbcTemplate 迁移到使用 Spring Data 的 CrudRepository 来访问数据库。CrudRepository 是 Spring Data 的一个接口,用于对特定类型的仓库进行通用的 CRUD 操作。它开箱即用地提供了多个与数据库交互的方法。

更新你的应用

首先,你需要调整 Message 类,使其适用于 CrudRepository API:

  1. 为 Message 类添加 @Table 注解,以声明到数据库表的映射。在 id 字段之前添加 @Id 注解。

注意: 这些注解还需要额外的导入。

1
2
3
4
5
6
7
8
    // Message.kt
    package com.example.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)

此外,为了让 Message 类的使用更符合语言习惯,你可以把 id 属性的默认值设为 null,并调换数据类属性的顺序:

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

现在,如果你需要创建 Message 类的新实例,只需把 text 属性作为参数指定即可:

1
    val message = Message("Hello") // id 为 null
  1. 为将要处理 Message 数据类的 CrudRepository 声明一个接口。创建 MessageRepository.kt 文件并添加以下代码:
1
2
3
4
5
6
    // MessageRepository.kt
    package com.example.demo

    import org.springframework.data.repository.CrudRepository

    interface MessageRepository : CrudRepository<Message, String>
  1. 更新 MessageService 类。现在它将使用 MessageRepository,而不是执行 SQL 查询:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    // MessageService.kt
    package com.example.demo

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

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

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

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

扩展函数

findByIdOrNull() 函数是 Spring Data JDBC 中 CrudRepository 接口的扩展函数。

CrudRepository 的 save() 函数

这个函数的工作方式假定新对象在数据库中没有 id。因此,插入时 id 应当为 null。 如果 id 不是 null,CrudRepository 会认为该对象已存在于数据库中,从而把这次操作视为更新而不是插入。插入操作完成后,id 会由数据存储生成并回填到该 Message 实例上。

  1. 更新 messages 表的定义,为插入的对象生成 id。由于 id 是字符串,你可以使用 RANDOM_UUID() 函数默认生成 id 值:
1
2
3
4
5
    -- schema.sql
    CREATE TABLE IF NOT EXISTS messages (
        id      VARCHAR(60)  DEFAULT RANDOM_UUID() PRIMARY KEY,
        text    VARCHAR      NOT NULL
    );
  1. 更新位于 src/main/resources 文件夹中的 application.properties 文件里的数据库名称:
   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

以下是应用的完整代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 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)
}
1
2
3
4
5
6
7
8
// Message.kt
package com.example.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)
1
2
3
4
5
6
// MessageRepository.kt
package com.example.demo

import org.springframework.data.repository.CrudRepository

interface MessageRepository : CrudRepository<Message, String>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// MessageService.kt
package com.example.demo

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

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

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

    fun save(message: Message): Message = db.save(message)
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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> =
        // 如果消息为 null(未找到),则把响应码设为 404
        this?.let { ResponseEntity.ok(it) } ?: ResponseEntity.notFound().build()
}

运行应用

恭喜!应用又可以运行了。用 CrudRepository 替换 JdbcTemplate 之后,功能保持不变,因此应用的工作方式与之前完全一样。

现在你可以从 requests.http 文件运行 POST 和 GET HTTP 请求,并得到相同的结果。

上一步

接下来学什么

获取你的专属语言地图,帮助你了解 Kotlin 特性并跟踪语言学习进度:

获取 Kotlin 语言地图