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!
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:
Kotlin1import 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.
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:
Kotlin1import 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
becomesperson
,TodoItem
becomestodo_item
, andOrderDetail
becomesorder_detail
.- Primary constructor with
@Id
specifies the primary key, whereid
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 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:
Kotlin1dependencies { 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:
Kotlin1dependencies { 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.
Here is how you can define an entity class using Spring Data JPA:
Kotlin1package 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.
To interact with the TodoItem
entity, define a repository interface:
Kotlin1package 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.
Here is how you can implement a simple CRUD controller to manage TodoItem
entities:
Kotlin1package 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 allTodoItem
instances.save(todoItem)
: Saves a new or updatedTodoItem
.findById(id)
: Fetches aTodoItem
by ID, showcasing Kotlin's null safety with optional handling.deleteById(id)
: Deletes aTodoItem
by its ID.
To understand the SQL operations that JPA performs, enable SQL logging in your application.properties
file:
.properties1spring.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.
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.