Kotlin + Spring Boot + Thymeleaf + JPA CRUD example
Hello everyone, Today we will learn how to develop a Kotlin + Spring Boot CRUD web application, using Thymeleaf view, H2DB, and Spring Data JPA.GitHub repository link is provided at the end of this tutorial. You can download the source code.
User Interface
Technologies used :
- Spring Boot 2.3.7.RELEASE
- Spring 5.2.12.RELEASE
- Kotlin 2.11.3
- Thymeleaf 5.3.0.11.RELEASE
- Hibernate 5.4.25.Final
- H2DB
- Maven 3
- Java 8
After completing this tutorial what we will build?
We will build a full-stack web application that is a basic User Management Application with CRUD features:
- Create User
- List User
- Update User
- Delete User
Project Structure
Maven[pom.xml]
A Project Object Model or POM is an XML file that contains information about the project and configuration details used by Maven to build the project.
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.7.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.knf.dev</groupId><artifactId>kotlin_springb_h2db_thymeleaf_crud</artifactId><version>0.0.1-SNAPSHOT</version><name>kotlin_springb_h2db_thymeleaf_crud</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><kotlin.version>1.3.72</kotlin.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>com.fasterxml.jackson.module</groupId><artifactId>jackson-module-kotlin</artifactId></dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-reflect</artifactId></dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-stdlib-jdk8</artifactId></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope><exclusions><exclusion><groupId>org.junit.vintage</groupId><artifactId>junit-vintage-engine</artifactId></exclusion></exclusions></dependency></dependencies><build><sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory><testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><plugin><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-maven-plugin</artifactId><configuration><args><arg>-Xjsr305=strict</arg></args><compilerPlugins><plugin>spring</plugin><plugin>jpa</plugin></compilerPlugins></configuration><dependencies><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-maven-allopen</artifactId><version>${kotlin.version}</version></dependency><dependency><groupId>org.jetbrains.kotlin</groupId><artifactId>kotlin-maven-noarg</artifactId><version>${kotlin.version}</version></dependency></dependencies></plugin></plugins></build></project>
Creating the Entity[User.kt]
The @Entity annotation specifies that the class is an entity and is mapped to a database table. The @Id annotation specifies the primary key of an entity and the @GeneratedValue provides for the specification of generation strategies for the values of primary keys.
package com.knf.dev.entity
import javax.persistence.*
@Entity@Table(name = "user")data class User(
@Id @GeneratedValue(strategy = GenerationType.AUTO) val id: Long = 0, val firstName: String, val lastName: String, val email: String)
package com.knf.dev.entity
import javax.persistence.*
@Entity
@Table(name = "user")
data class User(
@Id @GeneratedValue(strategy = GenerationType.AUTO)
val id: Long = 0,
val firstName: String,
val lastName: String,
val email: String
)
Creating the Repository[UserRepository.kt]
@Repository annotation is used to indicate that the class provides the mechanism for storage, retrieval, search, update and delete operation on objects.
package com.knf.dev.repository
import com.knf.dev.entity.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
@Repository
interface UserRepository : JpaRepository<User, Long>
Creating the controller End-points[UserController.kt]
The @RestController annotation was introduced in Spring 4.0 to simplify the engendering of RESTful web services. It's a convenience annotation that combines @Controller and @ResponseBody. @RequestMapping annotation maps HTTP requests to handler methods of MVC and REST controllers.
package com.knf.dev.controller
import com.knf.dev.entity.User
import com.knf.dev.repository.UserRepository
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.*
import org.springframework.ui.Model
import org.springframework.web.bind.annotation.GetMapping
import java.util.*
import org.springframework.web.bind.annotation.PathVariable
@Controller
class UserController(private val userRepository: UserRepository) {
@GetMapping("/")
fun getAllUsers(model: Model): String? {
val list: List<User> = userRepository.findAll()
model.addAttribute("users", list)
return "list-users"
}
@PostMapping(path = arrayOf("/createUser"))
fun createNewUser(@ModelAttribute user: User): String {
userRepository.save(user)
return "redirect:/";
}
@GetMapping(path = ["/add"])
fun addUserById(): String? {
return "add-user"
}
@GetMapping(path = ["/edit/{id}"])
fun editUserById(model: Model, @PathVariable("id") id: Long): String? {
model.addAttribute("user", userRepository.findById(id))
return "edit-user"
}
@PostMapping(path = ["/editUser"])
fun editUser(@ModelAttribute user: User): String? {
userRepository.save(user)
return "redirect:/"
}
@GetMapping(path = ["/delete/{id}"])
fun deleteUserById(@PathVariable("id") id: Long): String? {
userRepository.deleteById(id)
return "redirect:/"
}
}
Spring Boot Main
The @SpringBootApplication annotation is a convenience annotation that combines the @EnableAutoConfiguration, @Configuration and the @ComponentScan annotations.
package com.knf.dev
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class KotlinSpringbH2DBThymeleafApplication
fun main(args: Array<String>) {
runApplication<KotlinSpringbH2DBThymeleafApplication>(*args)
}
application.properties
spring.h2.console.enabled=true
spring.h2.console.path=/h2_console
spring.datasource.url=jdbc:h2:file:~/h2/knf-test
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql=true
add-user.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Add User</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="all" href="../../
bootstrap.min.css" th:href="@{/bootstrap.min.css}"/>
<link rel="stylesheet" href="https://use.fontawesome.com
/releases/v5.4.1/css/all.css">
</head>
<body>
<div class="container my-5">
<h3>Add User</h3>
<div class="card">
<div class="card-body">
<div class="col-md-10">
<form action="#" th:action="@{/createUser}" th:object="${user}"
method="post">
<div class="row">
<div class="form-group col-md-8">
<label for="firstName" class="col-form-label">
First Name</label>
<input type="text" name="firstName"
class="form-control" id="firstName"
placeholder="First Name"
/>
</div>
<div class="form-group col-md-8">
<label for="lastName" class="col-form-label">
Last Name</label>
<input type="text" name="lastName"
class="form-control" id="lastName"
placeholder="Last Name"/>
</div>
<div class="form-group col-md-8">
<label for="email" class="col-form-label">Email</label>
<input type="text" name="email"
class="form-control" id="email"
placeholder="Email Id"/>
</div>
<div class="col-md-6">
<input type="submit" class="btn btn-success"
value=" Submit ">
</div>
<input type="hidden" id="id">
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
edit-user.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Add User</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="all" href="../../
bootstrap.min.css" th:href="@{/bootstrap.min.css}"/>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/
v5.4.1/css/all.css">
</head>
<body>
<div class="container my-5">
<h3>Update User</h3>
<div class="card">
<div class="card-body">
<div class="col-md-10">
<form action="#" th:action="@{/editUser}" th:object="${user}"
method="post">
<div class="row">
<div class="form-group col-md-8">
<label for="firstName" class="col-form-label">
First Name</label>
<input type="text" th:field="*{firstName}"
class="form-control" id="firstName"
placeholder="First Name"
/>
</div>
<div class="form-group col-md-8">
<label for="lastName" class="col-form-label">
Last Name</label>
<input type="text" th:field="*{lastName}"
class="form-control" id="lastName"
placeholder="Last Name"/>
</div>
<div class="form-group col-md-8">
<label for="email" class="col-form-label">Email</label>
<input type="text" th:field="*{email}"
class="form-control" id="email"
placeholder="Email Id"/>
</div>
<div class="col-md-6">
<input type="submit" class="btn btn-success"
value=" Submit ">
</div>
<input type="hidden" th:field="*{id}" id="id">
</div>
</form>
</div>
</div>
</div>
</div>
</body>
</html>
list-users.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>All Users</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" media="all"
href="../../bootstrap.min.css" th:href="@{/bootstrap.min.css}" />
<link rel="stylesheet" href="https://use.fontawesome.com/releases
/v5.4.1/css/all.css">
</head>
<body>
<div class="container my-2">
<div class="card">
<div class="card-body">
<div th:switch="${users}" class="container my-5">
<p class="my-5">
<a href="/add" class="btn btn-success">
<i class="fas fa-user-plus ml-2"> Add User </i></a>
</p>
<div class="col-md-10">
<h2 th:case="null">No record found !!</h2>
<div th:case="*">
<table class="table table-striped table-responsive-md">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody>
<tr th:each="user : ${users}">
<td th:text="${user.firstName}"></td>
<td th:text="${user.lastName}"></td>
<td th:text="${user.email}"></td>
<td><a th:href="@{/edit/{id}(id=${user.id})}"
class="btn btn-warning"> <i
class="fas fa-user-edit ml-2"></i>
</a></td>
<td><a th:href="@{/delete/{id}(id=${user.id})}"
class="btn btn-danger"> <i
class="fas fa-user-times ml-2"></i>
</a></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
Run
Github repository download link is provided at the end of this tutorial
Github repository download link is provided at the end of this tutorial
Local Setup:
Step 1: Download or clone the source code to a local machine.
Spring Boot - Backend Project Setup
Step 2: mvn clean install
Step 3: Run the Spring Boot applicationmvn spring-boot:run
mvn spring-boot:run
Browse: http://localhost:8080/