Kotlin Help

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.

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

    Besides adding the annotations, you also need to make the id mutable (var) for the reasons of how CrudRepository works when inserting the new objects to the database.

  2. Declare an interface for the CrudRepository that will work with the Message data class:

    import org.springframework.data.repository.CrudRepository interface MessageRepository : CrudRepository<Message, String>
  3. Update the MessageService class. It will now call to the MessageRepository instead of executing SQL queries:

    import java.util.* @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() }
    Extension functions

    The return type of the findById() function in the CrudRepository interface is an instance of the Optional class. However, it would be convenient to return a List with a single message for consistency. For that, you need to unwrap the Optional value if it’s present, and return a list with the value. This can be implemented as an extension function to the Optional type.

    In the code, Optional<out T>.toList(), .toList() is the extension function for Optional. 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.

    CrudRepository save() function

    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:

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

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() }

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

Last modified: 14 December 2023