Lesson 1
Persisting Data with Spring Data JPA Using Kotlin
Introduction

Welcome to the first lesson of the course, "Persisting Data with Spring Data JPA." Throughout this course, you'll discover how to leverage Spring Data JPA to connect your Kotlin applications to relational databases, establish simple and complex relationships, implement pagination and sorting, and more. In this introductory lesson, we’ll delve into how Kotlin, being concise and interoperable with Java, makes working with Spring Data JPA efficient and less verbose. Kotlin's features, such as data classes and nullable types, complement the use of Spring, enhancing your productivity. Let’s get started!

Understanding JDBC

Java Database Connectivity (JDBC) is a Java Standard Edition (SE) API that allows Java applications to interact with relational databases. It involves writing explicit SQL queries, managing database connections, and handling result sets manually. Here is an example of how to retrieve data using JDBC:

Kotlin
1import java.sql.Connection 2import java.sql.DriverManager 3import java.sql.ResultSet 4import java.sql.Statement 5 6fun main() { 7 val url = "jdbc:mysql://localhost:3306/mydb" 8 val username = "root" 9 val password = "password" 10 11 try { 12 DriverManager.getConnection(url, username, password).use { conn -> 13 val stmt: Statement = conn.createStatement() 14 val rs: ResultSet = stmt.executeQuery("SELECT * FROM my_table") 15 16 while (rs.next()) { 17 println(rs.getString("column1")) 18 } 19 } 20 } catch (e: Exception) { 21 e.printStackTrace() 22 } 23}

As you can see, this approach is cumbersome with a lot of boilerplate code. This method has nothing to do with Spring Boot and reflects the complexities involved in manual database operations.

If you want to dive deeper into JDBC, refer to the official specification.

Simplifying Database Access with JPA

The Java Persistence API (JPA) provides a specification for Object Relational Mapping (ORM), simplifying database interactions by mapping Kotlin objects to database tables. ORM enables you to manipulate database records seamlessly through Kotlin objects, making your code cleaner and more maintainable. Here’s an example of a JPA entity class in Kotlin:

Kotlin
1import jakarta.persistence.Entity 2import jakarta.persistence.GeneratedValue 3import jakarta.persistence.GenerationType 4import jakarta.persistence.Id 5 6@Entity 7data class Person( 8 @Id 9 @GeneratedValue(strategy = GenerationType.IDENTITY) 10 val id: Long = 0, 11 var name: String, 12 var age: Int 13)

In this example:

  • @Entity: Denotes this class as a JPA entity. The default naming convention converts the class name from camel case to lower snake case to map it to a table. For instance, Person becomes person, TodoItem becomes todo_item, and OrderDetail becomes order_detail.
  • Primary constructor with @Id specifies the primary key, where id is mapped to the primary key column.
  • @GeneratedValue(strategy = GenerationType.IDENTITY): The primary key is auto-generated by the database.

For more on JPA, explore the official specification.

Spring Data JPA

Spring Data JPA is a powerful module that builds on top of JPA, reducing boilerplate code needed for database operations. It integrates seamlessly with the Spring framework, providing additional features such as derived queries, pagination, and auditing. The following are essential Gradle dependencies for including Spring Data JPA in your project:

Kotlin
1dependencies { 2 implementation("org.springframework.boot:spring-boot-starter-data-jpa") 3 implementation("com.h2database:h2") // H2 database dependency 4}

Explanation:

  • spring-boot-starter-data-jpa: Includes Spring Data JPA with Hibernate as the default JPA provider.
  • com.h2database:h2: JDBC driver for H2 database, an in-memory database that is quick and easy for development.

The H2 database is an open-source in-memory database which is particularly useful during development for its speed and simplicity. For more information, visit their official website.

Hibernate is the most commonly used JPA provider, but there are alternatives such as EclipseLink and OpenJPA. If you prefer to use a different JPA provider, you can exclude Hibernate and include your preferred provider in the dependencies:

If you prefer a different JPA provider, modify the dependencies:

Kotlin
1dependencies { 2 implementation("org.springframework.boot:spring-boot-starter-data-jpa") { 3 exclude(group = "org.hibernate", module = "hibernate-core") 4 } 5 implementation("com.h2database:h2") 6 implementation("org.eclipse.persistence:eclipselink:2.7.7") // EclipseLink JPA provider 7}

Explore more in the official documentation.

Creating Entity Class

Here is how you can define an entity class using Spring Data JPA:

Kotlin
1package com.codesignal.entities 2 3import jakarta.persistence.Entity 4import jakarta.persistence.GeneratedValue 5import jakarta.persistence.GenerationType 6import jakarta.persistence.Id 7 8@Entity 9data class TodoItem( 10 @Id 11 @GeneratedValue(strategy = GenerationType.IDENTITY) 12 val id: Long = 0, 13 var title: String, 14 var completed: Boolean 15)

As Spring Data JPA is an extension of JPA, the entity class will look exactly the same as if we were using basic JPA. In this class:

  • It’s marked as an entity using @Entity.
  • @Id declares the primary key.
  • @GeneratedValue(strategy = GenerationType.IDENTITY) specifies that the primary key is generated by the database.
Creating Repository Interface

To interact with the TodoItem entity, define a repository interface:

Kotlin
1package com.codesignal.repositories 2 3import com.codesignal.entities.TodoItem 4import org.springframework.data.jpa.repository.JpaRepository 5 6interface TodoRepository : JpaRepository<TodoItem, Long>

This interface extends JpaRepository<TodoItem, Long> (where Long is the type of the primary key of TodoItem), which provides CRUD operations and other methods out of the box. Spring Data JPA generates the actual implementation at runtime. When your Spring Boot application starts, it will create an instance of this interface, which you can then inject into your controllers or other beans using @Autowired or constructor injection.

Implementing the CRUD Controller

Here is how you can implement a simple CRUD controller to manage TodoItem entities:

Kotlin
1package com.codesignal.controllers 2 3import com.codesignal.entities.TodoItem 4import com.codesignal.repositories.TodoRepository 5import org.springframework.web.bind.annotation.* 6 7@RestController 8@RequestMapping("/todos") 9class TodoController(private val todoRepository: TodoRepository) { 10 11 @GetMapping 12 fun getAllTodos(): List<TodoItem> = todoRepository.findAll() 13 14 @PostMapping 15 fun createTodoItem(@RequestBody todoItem: TodoItem): TodoItem = todoRepository.save(todoItem) 16 17 @PutMapping("/{id}") 18 fun updateTodoItem(@PathVariable id: Long, @RequestBody todoItem: TodoItem): TodoItem { 19 return todoRepository.save(todoItem.copy(id = id)) 20 } 21 22 @GetMapping("/{id}") 23 fun getTodoItemById(@PathVariable id: Long): TodoItem? = todoRepository.findById(id).orElse(null) 24 25 @DeleteMapping("/{id}") 26 fun deleteTodoItem(@PathVariable id: Long) = todoRepository.deleteById(id) 27}

In this controller:

  • findAll(): Retrieves all TodoItem instances.
  • save(todoItem): Saves a new or updated TodoItem.
  • findById(id): Fetches a TodoItem by ID, showcasing Kotlin's null safety with optional handling.
  • deleteById(id): Deletes a TodoItem by its ID.
Show Hibernate/JPA SQL

To understand the SQL operations that JPA performs, enable SQL logging in your application.properties file:

.properties
1spring.jpa.show-sql=true # Logs SQL queries generated by JPA/Hibernate. 2spring.jpa.properties.hibernate.format_sql=true # Formats logged SQL for better readability.

This is essential for debugging and understanding database interactions.

Summary

In this lesson, we explored how Kotlin can be effectively used with Spring Data JPA, understanding the advantages of Kotlin's concise syntax and less boilerplate for database operations. We demonstrated defining a data class as a JPA entity, creating a Spring Data JPA repository interface, and implementing a CRUD controller with Kotlin’s idiomatic practices. Kotlin’s interoperability, null safety, and concise syntax enhance your productivity when working with Spring Data JPA immensely. In the next session, you'll experience these concepts in practice.

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.