Build REST CRUD APIs with Kotlin, Spring Boot and MyBatis
Hello everyone, today we will learn how to develop REST-style CRUD APIs with Spring Boot, Kotlin, MyBatis, and H2 Database. You can download the source code from our Github repository.
What is MyBatis?
MyBatis is a persistence framework with support for custom SQL, stored procedures and advanced mappings. MyBatis eliminates almost all of the JDBC code and manual setting of parameters and retrieval of results. MyBatis can use simple XML or Annotations for configuration and map primitives, Map interfaces and Java POJOs (Plain Old Java Objects) to database records.
Some of the features of MyBatis:
- In memory object filtering
- Fortifies clustering and simultaneous access by other applications without loss of transaction integrity
- Query Caching - Built-in support
- Fortifies disconnected operations
- Support for Remoting. Distributed Objects.
Technologies used :
- Spring Boot 2.5.4
- Spring 5.3.9
- MyBatis
- Kotlin
- Gradle
After completing this tutorial what we will build?
We will build REST API CRUD features:
- GET - Fetch all User : /api/v1/users
- GET - Get User by ID : /api/v1/users/{id}
- POST - Create User : /api/v1/users
- PUT - Edit User Details : /api/v1/users/{id}
- DELETE - Delete User : /api/v1/users/{id}
Project Directory:
Gradle Build(build.gradle.kts):
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
id("org.springframework.boot") version "2.5.4"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.5.21"
kotlin("plugin.spring") version "1.5.21"
}
group = "com.knf.dev.demo"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_11
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("org.jetbrains.kotlin:kotlin-reflect")
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
implementation("org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0")
runtimeOnly("com.h2database:h2")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = "11"
}
}
tasks.withType<Test> {
useJUnitPlatform()
}
Database Setup:
We will create a table called users with a few simple columns. We can initialize a schema by creating a schema.sql file in the resources.
create table users
(
id integer not null,
firstName varchar(255) not null,
lastName varchar(255) not null,
emailId varchar(255) not null,
primary key(id)
);
Create User Model:
package com.knf.dev.demo.model
class User {
var id: Long = 0
var firstName: String? = null
var lastName: String? = null
var emailId: String? = null
constructor() {}
constructor(id: Long, firstName: String?, lastName: String?,
emailId: String?) : super() {
this.id = id
this.firstName = firstName
this.lastName = lastName
this.emailId = emailId
}
}
Create User MyBatis Repository:
package com.knf.dev.demo.repository
import com.knf.dev.demo.model.User
import org.apache.ibatis.annotations.*
@Mapper
interface UserRepository {
@Select("select * from users")
fun findAll(): List<User?>?
@Select("SELECT * FROM users WHERE id = #{id}")
fun findById(id: Long): User?
@Delete("DELETE FROM users WHERE id = #{id}")
fun deleteById(id: Long): Int
@Insert(
"INSERT INTO users(id, firstName, lastName,emailId) " +
" VALUES (#{id}, #{firstName}, #{lastName}, #{emailId})"
)
fun insert(user: User?): Int
@Update(
"Update users set firstName=#{firstName}, " +
" lastName=#{lastName}, emailId=#{emailId} where id=#{id}"
)
fun update(user: User?): Int
}
Create User Rest Controller:
package com.knf.dev.demo.controller
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.beans.factory.annotation.Autowired
import com.knf.dev.demo.repository.UserRepository
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import com.knf.dev.demo.exception.UserIdAlreadyExistException
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.http.ResponseEntity
import com.knf.dev.demo.exception.UserIdNotFoundException
import com.knf.dev.demo.model.User
import org.springframework.web.bind.annotation.PutMapping
import org.springframework.web.bind.annotation.DeleteMapping
import java.util.HashMap
@RestController
@RequestMapping("/api/v1/")
class UserController {
@Autowired
private val userRepository: UserRepository? = null
// get all users
@get:GetMapping("/users")
val allUsers: List<User?>?
get() = userRepository!!.findAll()
// create user rest API
@PostMapping("/users")
fun createUser(@RequestBody user: User): User? {
return if (userRepository!!.findById(user.id) == null) {
val id = userRepository.insert(user)
userRepository.findById(user.id)
} else {
throw UserIdAlreadyExistException()
}
}
// get user by id rest api
@GetMapping("/users/{id}")
fun getUserById(@PathVariable id: Long?): ResponseEntity<User> {
val user = userRepository!!.findById(id!!) ?: throw UserIdNotFoundException()
return ResponseEntity.ok(user)
}
// update user rest api
@PutMapping("/users/{id}")
fun updateUser(
@PathVariable id: Long?,
@RequestBody userDetails: User
): ResponseEntity<User> {
if (userRepository!!.update(
User(
id!!, userDetails.firstName,
userDetails.lastName, userDetails.emailId
)
) == 0
) {
throw UserIdNotFoundException()
}
return ResponseEntity.ok(userRepository.findById(id))
}
// delete user rest api
@DeleteMapping("/users/{id}")
fun deleteUser(@PathVariable id: Long?): ResponseEntity<Map<String, Boolean>> {
userRepository!!.deleteById(id!!)
val response: MutableMap<String, Boolean> = HashMap()
response["deleted"] = java.lang.Boolean.TRUE
return ResponseEntity.ok(response)
}
}
Create Custom Exception:
UserIdNotFoundException:
package com.knf.dev.demo.exception
import java.lang.RuntimeException
class UserIdNotFoundException : RuntimeException("User Id Not Found")
UserIdAlreadyExistException:
package com.knf.dev.demo.exception
import java.lang.RuntimeException
class UserIdAlreadyExistException : RuntimeException("User Id Already Exist")
GlobalExceptionHandler:
package com.knf.dev.demo.exception
import org.springframework.web.bind.annotation.ControllerAdvice
import com.knf.dev.demo.exception.UserIdNotFoundException
import org.springframework.web.context.request.WebRequest
import org.springframework.http.ResponseEntity
import java.time.LocalDateTime
import org.springframework.http.HttpStatus
import com.knf.dev.demo.exception.UserIdAlreadyExistException
import org.springframework.web.bind.annotation.ExceptionHandler
import java.lang.Exception
@ControllerAdvice
class GlobalExceptionHandler {
@ExceptionHandler(UserIdNotFoundException::class)
fun globalExceptionHandler(ex: Exception, request: WebRequest?):
ResponseEntity<CustomErrorResponse> {
val errors = CustomErrorResponse()
errors.timestamp = LocalDateTime.now()
errors.error = ex.message
errors.status = HttpStatus.NOT_FOUND.value()
return ResponseEntity<CustomErrorResponse>(errors, HttpStatus.NOT_FOUND)
}
@ExceptionHandler(UserIdAlreadyExistException::class)
fun globalExceptionHandler2(ex: Exception, request: WebRequest?):
ResponseEntity<CustomErrorResponse> {
val errors = CustomErrorResponse()
errors.timestamp = LocalDateTime.now()
errors.error = ex.message
errors.status = HttpStatus.INTERNAL_SERVER_ERROR.value()
return ResponseEntity<CustomErrorResponse>(errors,
HttpStatus.INTERNAL_SERVER_ERROR)
}
}
CustomErrorResponse:
package com.knf.dev.demo.exception
import com.fasterxml.jackson.annotation.JsonFormat
import java.time.LocalDateTime
class CustomErrorResponse {
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd hh:mm:ss")
var timestamp: LocalDateTime? = null
var status = 0
var error: String? = null
}
Spring Boot Main Class:
package com.knf.dev.demo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
@SpringBootApplication
class KotlinSpringbootMybatisCrudExampleApplication
fun main(args: Array<String>) {
runApplication<KotlinSpringbootMybatisCrudExampleApplication>(*args)
}
Run Spring Boot application:
More related topics,