Build REST CRUD APIs with Spring Boot, MyBatis(XML Mapping), & PostgreSQL

In this section, we will learn how to develop a REST-style web service with Spring BootMyBatis(XML Mapping) and PostgreSQL. GitHub repository link is provided at the end of this tutorial. You can download the source code. 


Technologies used:

  • Spring Boot 3.0.1
  • MyBatis 3
  • Java 17
  • Maven 
  • PostgreSQL driver and database

These are APIs that Spring Boot backend App will export:



Creating a simple spring boot web application:

First, open the Spring initializr https://start.spring.io/ 

Then, Provide the Group and Artifact name. We have provided Group name com.knf.dev.demo and Artifact spring-boot-postgresql-mybatis-xml-crud. Here I selected the Maven project - language Java 17 - Spring Boot 3.0.1 and add Spring web dependency, MyBatis Framework and PostgreSQL Driver.



Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-boot-postgresql-mybatis-xml-crud) file and downloads the project. Then, Extract the Zip file. 
Then, import the project on your favourite IDE.

Final Project directory:



Create database.

First, you need to create a database in the PostgreSQL. 
CREATE DATABASE testdb;

pom.xml

<?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.0
https://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>3.0.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-boot-postgresql-mybatis-xml-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-postgresql-mybatis-xml-crud</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

schema.sql

CREATE TABLE IF NOT EXISTS users
(
id SERIAL PRIMARY KEY,
first_name varchar(255) not null,
last_name varchar(255) not null,
email_id varchar(255) not null
);

application.properties

mybatis.mapper-locations is a key configuration. It specifies the path to load the xml file for the mapper interface (wildcards can be used).

Use mybatis.type-aliases-package to specify the POJO scan package so that mybatis automatically scans for custom POJOs.

spring.datasource.url=jdbc:postgresql://localhost:5432/testdb
spring.datasource.username=postgres
spring.datasource.password=root

spring.sql.init.mode=always

#Configure the xml Mapping path
mybatis.mapper-locations=classpath:mapper/UserMapper.xml
#Configure entity category names
mybatis.type-aliases-package=com.knf.dev.demo.model

Creating User Bean

package com.knf.dev.demo.model;

public class User {

private Integer id;
private String firstName;
private String lastName;
private String emailId;

public User() {
}

public User(Integer id,String firstName,
String lastName, String emailId) {
super();
this.id=id;
this.firstName = firstName;
this.lastName = lastName;
this.emailId = emailId;
}

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getEmailId() {
return emailId;
}

public void setEmailId(String emailId) {
this.emailId = emailId;
}
}

Create User Repository

package com.knf.dev.demo.repository;

import com.knf.dev.demo.model.User;
import java.util.List;
import java.util.Optional;

public interface UserRepository {

List<User> findAll();

Optional<User> findById(Integer id);

int deleteById(Integer id);

int insert(User user);

int update(User user);
}

UserMapper.xml

Mapper XML is an important file in MyBatis, which contains a set of statements to configure various SQL statements such as select, insert, update, and delete.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.knf.dev.demo.repository.UserRepository">
<resultMap type="user" id="result">
<id property="id" column="id" />
<result property="firstName" column="first_name" />
<result property="lastName" column="last_name" />
<result property="emailId" column="email_id" />
</resultMap>

<select id="findAll" resultType="user" resultMap="result">
SELECT * FROM users
</select>

<insert id="insert" parameterType="user"
keyProperty="id" useGeneratedKeys="true">
INSERT INTO users(first_name, last_name, email_id)
VALUES(#{firstName}, #{lastName}, #{emailId})
</insert>

<update id="update" parameterType="user">
UPDATE users SET first_name = #{firstName},
email_id = #{emailId}, last_name = #{lastName}
WHERE id = #{id}
</update>

<delete id="deleteById" parameterType="int">
DELETE FROM users WHERE id = #{id}
</delete>

<select id="findById" parameterType="int"
resultType="user" resultMap="result">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>

-Exception handling

Create ResourceNotFoundException

package com.knf.dev.demo.exception;

public class ResourceNotFoundException extends RuntimeException{
private static final long serialVersionUID = 1L;

public ResourceNotFoundException(String message) {
super(message);
}
}

Create GlobalExceptionHandler

Spring supports exception handling by a Global Exception Handler (@ExceptionHandler) with Controller Advice (@ControllerAdvice). This enables a mechanism that makes ResponseEntity work with the type safety and flexibility of @ExceptionHandler.
package com.knf.dev.demo.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import java.time.LocalDateTime;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<CustomErrorResponse>
globalExceptionHandler(Exception ex, WebRequest request) {
CustomErrorResponse errors = new CustomErrorResponse();
errors.setTimestamp(LocalDateTime.now());
errors.setError(ex.getMessage());
errors.setStatus(HttpStatus.NOT_FOUND.value());
return new ResponseEntity<>(errors, HttpStatus.NOT_FOUND);
}
}

Create CustomErrorResponse

package com.knf.dev.demo.exception;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.time.LocalDateTime;

public class CustomErrorResponse {

@JsonFormat(shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime timestamp;
private int status;
private String error;
public LocalDateTime getTimestamp()
{
return timestamp;
}
public void setTimestamp(LocalDateTime timestamp)
{
this.timestamp = timestamp;
}
public int getStatus()
{
return status;
}
public void setStatus(int status)
{
this.status = status;
}
public String getError()
{
return error;
}
public void setError(String error)
{
this.error = error;
}
}

Create User Rest Controller

package com.knf.dev.demo.controller;

import com.knf.dev.demo.exception.ResourceNotFoundException;
import com.knf.dev.demo.model.User;
import com.knf.dev.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/v1")
public class UserController {
@Autowired
private UserRepository userRepository;

// get all users
@GetMapping("/users")
public List<User> getAllUsers()
{
return userRepository.findAll();
}

// create user rest API
@PostMapping("/users")
public Map<String, Boolean> createUser(@RequestBody User user) {

Map<String, Boolean> response = new HashMap<>();

Boolean bool = userRepository.insert(user) > 0 ?
response.put("created", Boolean.TRUE) :
response.put("created", Boolean.FALSE);

return response;

}

// get user by id rest api
@GetMapping("/users/{id}")
public User findUserById(@PathVariable Integer id) {

User user = userRepository.findById(id).
orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
return user;
}

// update user rest api
@PutMapping("/users/{id}")
public Map<String, Boolean> updateUser(@PathVariable Integer id,
@RequestBody User userDetails) {

User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));
userDetails.setId(id);
Map<String, Boolean> response = new HashMap<>();

Boolean bool = userRepository.update(userDetails) > 0 ?
response.put("updated", Boolean.TRUE) :
response.put("updated", Boolean.FALSE);

return response;
}

// delete user rest api
@DeleteMapping("/users/{id}")
public Map<String, Boolean> deleteUser
(@PathVariable Integer id) {

User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException
("User not exist with id :" + id));

Map<String, Boolean> response = new HashMap<>();

Boolean bool = userRepository.deleteById(user.getId()) > 0 ?
response.put("deleted", Boolean.TRUE) :
response.put("deleted", Boolean.FALSE);
return response;
}
}

Run the application - Application.java

Set the package where the mapper interface resides (there can be more than one) via the @MapperScan annotation.
package com.knf.dev.demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.knf.dev.demo.repository")
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Application is the entry point that sets up the Spring Boot application. The @SpringBootApplication annotation enables auto-configuration and component scanning. 

Step1: Download or clone the source code from GitHub to a local machine - Click here


Step 2: mvn clean install


Step 3: Run the Spring Boot application - mvn spring-boot:run


OR 


Run this Spring boot application from 

  • IntelliJ IDEA IDE by right click - Run 'Application.main()' 
  • Eclipse/STS - You can right click the project or the Application.java file and run as java application or Spring boot application.


Add User-

Fetch all User-

Get User by ID-

Update User-

Delete User by ID-

More....

Popular posts from this blog

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

Java Stream API - How to convert List of objects to another List of objects using Java streams?

Registration and Login with Spring Boot + Spring Security + Thymeleaf

Java, Spring Boot Mini Project - Library Management System - Download

ReactJS, Spring Boot JWT Authentication Example

Spring Boot + Mockito simple application with 100% code coverage

Top 5 Java ORM tools - 2024

Java - Blowfish Encryption and decryption Example

Spring boot video streaming example-HTML5

Google Cloud Storage + Spring Boot - File Upload, Download, and Delete