# 使用 Spring Data CrudRepository 进行数据库访问 在这一章中, 你将会迁移服务层, 使用 [Spring Data](https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html) `CrudRepository` 进行数据库访问, 而不是原来的 `JdbcTemplate` . CrudRepository 是一个 Spring Data 接口, 可以指定类型的仓库进行通常的 [CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete) 操作. 它提供了一些现成的方法来操作数据库. ## 更新你的应用程序 首先, 你需要调整 `Message` 类, 来配合 `CrudRepository` API: 1. 向 `Message` 类添加 `@Table` 注解, 声明它与数据库表的映射关系. 在 `id` 属性之前添加 `@Id` 注解. > **Note:** > 这些注解也需要额外的 import. ```KOTLIN // 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` 类的使用更加符合 Kotlin 的编程习惯, 你可以将 `id` 属性的默认值设置为 null, 并翻转数据类的属性顺序: ```KOTLIN @Table("MESSAGES") data class Message(val text: String, @Id val id: String? = null) ``` 现在, 如果你需要创建的 `Message` 类的新实例, 你可以在参数中只指定 `text` 属性: ```KOTLIN val message = Message("Hello") // id 为 null ``` 2. 为 `CrudRepository` 声明一个接口, 它负责操作 `Message` 数据类. 创建 `MessageRepository.kt` 文件, 添加以下代码: ```KOTLIN // MessageRepository.kt package com.example.demo import org.springframework.data.repository.CrudRepository interface MessageRepository : CrudRepository ``` 3. 更新 `MessageService` 类. 它现在使用 `MessageRepository`, 而不是执行 SQL 查询: ```KOTLIN // 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 = db.findAll().toList() fun findMessageById(id: String): Message? = db.findByIdOrNull(id) fun save(message: Message): Message = db.save(message) } ``` 扩展函数 : `findByIdOrNull()` 函数是 Spring Data JDBC 中的 `CrudRepository` 接口的一个 [扩展函数](extensions.html#extension-functions). : : : : 在上面的代码中, `Optional.toList()`, `.toList()` 是 `Optional` 的扩展函数. 使用扩展函数, 你可以向任何类添加额外的函数, 当你想要扩展某些库中的类的功能时, 这样会非常有用. CrudRepository save() 函数 : [这个函数的工作方式](https://docs.spring.io/spring-data/relational/reference/#jdbc.entity-persistence) 是假定新的对象在数据库中没有 id. 因此, 对 insertion 操作, id 需要为 null. : : : : 如果 id 不是 null, `CrudRepository` 假定对象在数据库中已经存在, 并且这是一个 update 操作, 而不是 insert 操作. 在 insert 操作之后, `id` 会由数据库生成, 并反过来赋值给 `Message` 实例. 4. 更新 messages 表定义, 对 insert 的对象生成 id. 由于 `id` 是一个字符串, 你可以使用 `RANDOM_UUID()` 函数来生成默认的 id 值: ```SQL -- schema.sql CREATE TABLE IF NOT EXISTS messages ( id VARCHAR(60) DEFAULT RANDOM_UUID() PRIMARY KEY, text VARCHAR NOT NULL ); ``` 5. 更新 `src/main/resources` 文件夹中的 `application.properties` 文件内的数据库名称: ```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 ``` 下面是应用程序的完整代码: ```KOTLIN // DemoApplication.kt package com.example.demo import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class DemoApplication fun main(args: Array) { runApplication(*args) } ``` ```KOTLIN // 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) ``` ```KOTLIN // MessageRepository.kt package com.example.demo import org.springframework.data.repository.CrudRepository interface MessageRepository : CrudRepository ``` ```KOTLIN // 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 = db.findAll().toList() fun findMessageById(id: String): Message? = db.findByIdOrNull(id) fun save(message: Message): Message = db.save(message) } ``` ```KOTLIN // 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 { 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 = // 如果 message 为 null (未找到), 将应答的 Status Code 设置为 404 this?.let { ResponseEntity.ok(it) } ?: ResponseEntity.notFound().build() } ``` ## 运行应用程序 恭喜! 应用程序可以再次运行了. 在将 `JdbcTemplate` 替换为 `CrudRepository` 之后, 功能没有改变, 因此应用程序会和以前一样运行. 现在你可以从 `requests.http` 文件 [运行 POST 和 GET HTTP 请求](jvm-spring-boot-add-db-support.html#add-messages-to-database-via-http-request), 并得到相同的结果. * [上一步](jvm-spring-boot-add-db-support.html) ## 下一步做什么 得到你个人的语言导航地图, 它可以帮助你浏览 Kotlin 的功能特性, 并追踪你学习语言的进度: [得到 Kotlin 语言导航地图](https://resources.jetbrains.com/storage/products/kotlin/docs/Kotlin_Language_Features_Map.pdf) * 阅读 [Spring Framework](https://docs.spring.io/spring-framework/reference/) 文档. * 学习教程 [Securing a web application](https://spring.io/guides/gs/securing-web), 创建一个带有受保护资源的简单 Web 应用程序. * 完成教程 [使用 Spring Boot 和 Kotlin 创建 Web 应用程序](https://spring.io/guides/tutorials/spring-boot-kotlin).