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: 
  1. GET - Fetch all User       :     /api/v1/users
  2. GET - Get User by ID     :     /api/v1/users/{id} 
  3. POST - Create User         :     /api/v1/users 
  4. PUT - Edit User Details   :     /api/v1/users/{id} 
  5. 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:

Testing APIs using Postman

Create a User:

Fetch all the users:

Get user by id:

Update User:

Delete User:




Popular posts from this blog

Spring boot video streaming example-HTML5

Learn Java 8 streams with an example - print odd/even numbers from Array and List

Spring Boot + Mockito simple application with 100% code coverage

Spring Boot + OpenCSV Export Data to CSV Example

Custom Exception Handling in Quarkus REST API

DataTable-Pagination example with Spring boot, jQuery and ajax

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Node JS mini projects with source code - free download

Spring boot web project free download:User Registration System

Java - Blowfish Encryption and decryption Example