Spring Boot + Spring Data JDBC + Microsoft SQL Server - Build REST CRUD APIs
In this section, we will learn how to develop a REST-style web service with Spring Boot, Spring Data JDBC, and Microsoft SQL Server. GitHub repository link is provided at the end of this tutorial. You can download the source code.
*Before development, make sure that the Microsoft SQL server is installed on your machine - SQL Server Downloads | Microsoft
Technologies used:
- Spring Boot 3.0.2
- Spring Data JDBC
- Java 17
- Maven
- Microsoft SQL Server Driver
- Microsoft SQL Server
These are APIs that Spring Boot backend App will export:
1. Create database.
First, you need to create a database in the Microsoft SQL Server.
CREATE DATABASE testdb;
2. 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-spring-data-jdbc-sqlserver-crud. Here I selected the Maven project - language Java 17 - Spring Boot 3.0.2 and add Spring web dependency, Spring Data JDBC, and MS SQL Server Driver.
Final Project directory:
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.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-boot-spring-data-jdbc-sqlserver-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-spring-data-jdbc-sqlserver-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-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</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>
application.properties
Add the following configuration to application.properties file:
spring.datasource.url= jdbc:sqlserver://localhost:1433;databaseName=testdb;
spring.datasource.username= knfuser
spring.datasource.password= root
spring.sql.init.mode=always
- Specify Data Source Properties like url, username, and password.
- The configuration property spring.sql.init.mode=always means that Spring Boot will automatically generate a database schema, using the schema.sql file that we will create later, each time the server is started.
schema.sql
IF OBJECT_ID(N'users', N'U') IS NULL
CREATE TABLE users (
id INT NOT NULL IDENTITY PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100),
email VARCHAR(100) NOT NULL,
);
Create User model - User.java
package com.knf.dev.demo.model;
public class User {
private Long id;
private String firstName;
private String lastName;
private String email;
public User(Long id, String firstName, String lastName, String email) {
this.id = id;
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long 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 getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
Create User Repository - UserRepository.java
package com.knf.dev.demo.repository;
import com.knf.dev.demo.model.User;
import java.util.List;
public interface UserRepository {
public User findById(Long id);
public List<User> findAll();
public int insert(User user);
public int update(User user);
public int deleteById(Long id);
}
package com.knf.dev.demo.repository;
import com.knf.dev.demo.model.User;
import java.util.List;
public interface UserRepository {
public User findById(Long id);
public List<User> findAll();
public int insert(User user);
public int update(User user);
public int deleteById(Long id);
}
Create UserRepositoryImpl.java
package com.knf.dev.demo.repository;
import com.knf.dev.demo.exception.ResourceNotFoundException;
import com.knf.dev.demo.model.User;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class UserRepositoryImpl implements UserRepository{
private final JdbcTemplate jdbcTemplate;
public UserRepositoryImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public User findById(Long id) {
String sqlQuery = Query.FIND_ONE;
User user = null;
try {
user = jdbcTemplate.
queryForObject(sqlQuery,
this::mapRowToUser, id);
}catch(EmptyResultDataAccessException e)
{
throw new ResourceNotFoundException
("User not exist with id :" + id);
}
return user;
}
@Override
public List<User> findAll() {
String sqlQuery = Query.FIND_ALL;
return jdbcTemplate.
query(sqlQuery, this::mapRowToUser);
}
@Override
public int insert(User user) {
String sqlQuery = Query.SAVE;
return jdbcTemplate.update(sqlQuery,
user.getFirstName(),
user.getLastName(),
user.getEmail());
}
@Override
public int update(User user) {
String sqlQuery = Query.UPDATE;
return jdbcTemplate.update(sqlQuery,
user.getFirstName(),
user.getLastName(),
user.getEmail(),
user.getId());
}
@Override
public int deleteById(Long id) {
String sqlQuery = Query.DELETE;
return jdbcTemplate.update(sqlQuery, id);
}
private User mapRowToUser(ResultSet resultSet, int rowNum)
throws SQLException {
User user = new User(resultSet.getLong("id"),
resultSet.getString("first_name"),
resultSet.getString("last_name"),
resultSet.getString("email"));
return user;
}
}
- JdbcTemplate is a powerful mechanism to connect to the database and execute SQL queries. It internally uses JDBC api, but eliminates a lot of problems of JDBC API.
- The @Repository annotation is a specialization of the @Component annotation which indicates that an annotated class is a "Repository".
Query.java
package com.knf.dev.demo.repository;
public class Query {
public static final String FIND_ONE =
"select id, first_name, last_name,"
+ " email from users where id = ?";
public static final String FIND_ALL =
"select id, first_name,last_name,"
+ " email from users";
public static final String SAVE =
"insert into users(first_name, "
+ "last_name, email) "
+ "values (?, ?, ?)";
public static final String UPDATE =
"update users set "
+ "first_name = ?, last_name = ?,"
+ " email = ? " + "where id = ?";
public static final String DELETE =
"delete from users where id = ?";
}
-Exception Handling
Create custom Exception -ResourceNotFoundException.java
package com.knf.dev.demo.exception;
public class ResourceNotFoundException extends RuntimeException{
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message) {
super(message);
}
}
Create Custom Error Response
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 Global Exception Handler
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);
}
}
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.
Create User Controller
package com.knf.dev.demo.controller;
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 Long id) {
User user = userRepository.findById(id);
return user;
}
// update user rest api
@PutMapping("/users/{id}")
public Map<String, Boolean> updateUser(@PathVariable Long id,
@RequestBody User userDetails) {
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 Long id) {
Map<String, Boolean> response = new HashMap<>();
Boolean bool = userRepository.deleteById(id) > 0 ?
response.put("deleted", Boolean.TRUE) :
response.put("deleted", Boolean.FALSE);
return response;
}
}
- Spring @RestController annotation is used to create RESTful web services using Spring MVC. Spring RestController takes care of mapping request data to the defined request handler method. Once response body is generated from the handler method, it converts it to JSON response.
- @RequestMapping is used to map web requests onto specific handler classes and/or handler methods. @RequestMapping can be applied to the controller class as well as methods.
- We can use the @Autowired to mark a dependency which Spring is going to resolve and inject.
- @GetMapping annotation for mapping HTTP GET requests onto specific handler methods.
- @PostMapping annotation for mapping HTTP POST requests onto specific handler methods.
- @PutMapping annotation for mapping HTTP PUT requests onto specific handler methods.
- @DeleteMapping annotation for mapping HTTP DELETE requests onto specific handler methods.
- @RequestBody annotation is used to indicating a method parameter should be bind to the body of the HTTP request. Internally, this annotation uses HTTP Message converters to convert the body of HTTP requests to domain objects.
- @PathVariable annotation used on a method argument to bind it to the value of a URI template variable.
Run the application - Application.java
package com.knf.dev.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
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.
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:
More....