Spring JDBC API + Azure SQL Database - Build REST CRUD APIs
In this section, we will learn how to build REST CRUD APIs with Spring Boot, Spring JDBC API and Microsoft Azure SQL Database.
A little bit of Background
Azure SQL Database
Azure SQL Database is a fully managed platform as a service (PaaS) database engine that handles most of the database management functions such as upgrading, patching, backups, and monitoring without user involvement. Azure SQL Database is always running on the latest stable version of the SQL Server database engine and patched OS with 99.99% availability. PaaS capabilities built into Azure SQL Database enable you to focus on the domain-specific database administration and optimization activities that are critical for your business.
Spring Boot
Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run". More Info - Spring Boot
Spring Boot makes it easy to create stand-alone, production-grade Spring-based Applications that you can "just run".
More Info - Spring Boot
Spring JDBC API
The Spring Jdbc API is an adaptation of the standard JDBC API. It is very easy to get started using Spring Jdbc, and the API includes many useful methods that will map data to classes that Java developers use on a regular basis.
More Info - Spring JDBC Tutorial | Baeldung
Step 1: Sign in to Azure Portal and create a resource group
Sign in to Azure portal https://portal.azure.com/#home and find "Resource groups" like below.
Then, create a resource group like the one below.
Step 2: Create an Azure SQL Database
Select "Azure SQL",
You will be taken to a page like the below image, Then click on the "Create Azure SQL resource" button.
You will be taken to a page like the below image,
Select/Enter necessary information like the above image and then, click on the "Review + create" button. Don't forget to keep the "Server admin login" name and "Password" somewhere safe.
Then click on the "Create" button.
Now, You can see "Deployment is in progress" like the below image.
Then, click on the "Go to resource" button, and you will be taken to a page like the below image,
Create SQL Database,
Then click on the "Create" button. You can see "Deployment is in progress"
Once deployment is completed you can see the "Your deployment is complete" page like the below image.
Whitelisting IP Address for Access to Azure SQL DB, like the below image
Step 3: 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-jdbc-api-azure-sql-crud. Here I selected the Maven project - language Java - Spring Boot 2.7.3 and add Spring web dependency, MS SQL Server Driver, and JDBC API.
Then, click on the Generate button. When we click on the Generate button, it starts packing the project in a .zip(spring-jdbc-api-azure-sql-crud.zip) file and downloads the project.
Then, Extract the Zip file.
Import the project on your favourite IDE,
Final Project Directory:
Pom.xml
A Project Object Model or POM is the fundamental unit of work in Maven. It is an XML file that contains information about the project and configuration details utilized 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.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>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-jdbc-api-azure-sql-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-jdbc-api-azure-sql-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-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>
<?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>2.7.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knf.dev.demo</groupId>
<artifactId>spring-jdbc-api-azure-sql-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-jdbc-api-azure-sql-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-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>
schema.sql: Creating a table for 'users'
Spring Boot can automatically create the schema (DDL scripts) of your DataSource and initialize it (DML scripts). It loads SQL from the standard root classpath locations: schema. sql and data. sql , respectively.DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT IDENTITY PRIMARY KEY,
first_name VARCHAR ( 50 ) NOT NULL,
last_name VARCHAR ( 50 ) NOT NULL,
email VARCHAR ( 255 ) NOT NULL
);
Spring Boot can automatically create the schema (DDL scripts) of your DataSource and initialize it (DML scripts). It loads SQL from the standard root classpath locations: schema. sql and data. sql , respectively.
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT IDENTITY PRIMARY KEY,
first_name VARCHAR ( 50 ) NOT NULL,
last_name VARCHAR ( 50 ) NOT NULL,
email VARCHAR ( 255 ) NOT NULL
);
application.properties
The following properties are needed to configure Azure SQL with Spring Boot.
The property spring.datasource.initialization-mode from Spring boot verion 2.7 and onwards is not any more depracated. It has been completely removed! So the change into the replacement property spring.sql.init.mode is a must do from now on.Example: spring.sql.init.mode=alwayslogging.level.org.springframework.jdbc.core=DEBUG
spring.datasource.url=jdbc:sqlserver://<servername>.database.windows.net:1433;database=demo_db;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;
spring.datasource.username=<username>
spring.datasource.password=<password>
spring.sql.init.mode=always
**Important note** Do not publish the fields directly for security. It would be a good choice to define these variables as environment variables or use the Azure Key Vault.
The following properties are needed to configure Azure SQL with Spring Boot.
The property spring.datasource.initialization-mode from Spring boot verion 2.7 and onwards is not any more depracated. It has been completely removed! So the change into the replacement property spring.sql.init.mode is a must do from now on.
Example: spring.sql.init.mode=always
logging.level.org.springframework.jdbc.core=DEBUG
spring.datasource.url=jdbc:sqlserver://<servername>.database.windows.net:1433;database=demo_db;encrypt=true;trustServerCertificate=false;hostNameInCertificate=*.database.windows.net;loginTimeout=30;
spring.datasource.username=<username>
spring.datasource.password=<password>
spring.sql.init.mode=always
**Important note** Do not publish the fields directly for security. It would be a good choice to define these variables as environment variables or use the Azure Key Vault.
Creating the Model User
package com.knf.dev.demo.model;
public record User
(Long id,
String firstName,
String lastName,
String email) {
}
package com.knf.dev.demo.model;
public record User
(Long id,
String firstName,
String lastName,
String email) {
}
Creating the User Repository
package com.knf.dev.demo.repository;
import com.knf.dev.demo.model.User;
import java.util.List;
public interface UserRepository {
public User findOne(Long id);
public List<User> findAll();
public void save(User user);
public Long saveAndReturnId(User user);
public void update(User user);
public Boolean delete(Long id);
}
package com.knf.dev.demo.repository;
import com.knf.dev.demo.model.User;
import java.util.List;
public interface UserRepository {
public User findOne(Long id);
public List<User> findAll();
public void save(User user);
public Long saveAndReturnId(User user);
public void update(User user);
public Boolean delete(Long id);
}
Creating the User Repository Implementation
package com.knf.dev.demo.repository;
import com.knf.dev.demo.exception.UserNotFound;
import com.knf.dev.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class UserRepositoryImpl implements UserRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public UserRepositoryImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public User findOne(Long id) {
var sqlQuery = Query.FIND_ONE;
try {
return jdbcTemplate.
queryForObject(sqlQuery,
this::mapRowToUser, id);
} catch (EmptyResultDataAccessException ex) {
throw new UserNotFound("Invalid User Id");
}
}
@Override
public List<User> findAll() {
var sqlQuery = Query.FIND_ALL;
return jdbcTemplate.
query(sqlQuery, this::mapRowToUser);
}
@Override
public void save(User user) {
var sqlQuery = Query.SAVE;
jdbcTemplate.update(sqlQuery,
user.firstName(),
user.lastName(),
user.email());
}
@Override
public Long saveAndReturnId(User user) {
var sqlQuery = Query.SAVE_AND_RETURN_ID;
var keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement stmt = connection.
prepareStatement(sqlQuery,
new String[] { "id" });
stmt.setString(1, user.firstName());
stmt.setString(2, user.lastName());
stmt.setString(3, user.email());
return stmt;
}, keyHolder);
return keyHolder.getKey().longValue();
}
@Override
public void update(User user) {
var sqlQuery = Query.UPDATE;
jdbcTemplate.update(sqlQuery,
user.firstName(),
user.lastName(),
user.email(),
user.id());
}
@Override
public Boolean delete(Long id) {
var sqlQuery = Query.DELETE;
return jdbcTemplate.update(sqlQuery, id) > 0;
}
private User mapRowToUser(ResultSet resultSet, int rowNum)
throws SQLException {
var user = new User(resultSet.getLong("id"),
resultSet.getString("first_name"),
resultSet.getString("last_name"),
resultSet.getString("email"));
return user;
}
}
package com.knf.dev.demo.repository;
import com.knf.dev.demo.exception.UserNotFound;
import com.knf.dev.demo.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Repository;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
@Repository
public class UserRepositoryImpl implements UserRepository {
private final JdbcTemplate jdbcTemplate;
@Autowired
public UserRepositoryImpl(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public User findOne(Long id) {
var sqlQuery = Query.FIND_ONE;
try {
return jdbcTemplate.
queryForObject(sqlQuery,
this::mapRowToUser, id);
} catch (EmptyResultDataAccessException ex) {
throw new UserNotFound("Invalid User Id");
}
}
@Override
public List<User> findAll() {
var sqlQuery = Query.FIND_ALL;
return jdbcTemplate.
query(sqlQuery, this::mapRowToUser);
}
@Override
public void save(User user) {
var sqlQuery = Query.SAVE;
jdbcTemplate.update(sqlQuery,
user.firstName(),
user.lastName(),
user.email());
}
@Override
public Long saveAndReturnId(User user) {
var sqlQuery = Query.SAVE_AND_RETURN_ID;
var keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement stmt = connection.
prepareStatement(sqlQuery,
new String[] { "id" });
stmt.setString(1, user.firstName());
stmt.setString(2, user.lastName());
stmt.setString(3, user.email());
return stmt;
}, keyHolder);
return keyHolder.getKey().longValue();
}
@Override
public void update(User user) {
var sqlQuery = Query.UPDATE;
jdbcTemplate.update(sqlQuery,
user.firstName(),
user.lastName(),
user.email(),
user.id());
}
@Override
public Boolean delete(Long id) {
var sqlQuery = Query.DELETE;
return jdbcTemplate.update(sqlQuery, id) > 0;
}
private User mapRowToUser(ResultSet resultSet, int rowNum)
throws SQLException {
var user = new User(resultSet.getLong("id"),
resultSet.getString("first_name"),
resultSet.getString("last_name"),
resultSet.getString("email"));
return user;
}
}
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 SAVE_AND_RETURN_ID =
"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 = ?";
}
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 SAVE_AND_RETURN_ID =
"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 Handler with Controller Advice in Spring
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.util.Date;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFound.class)
public ResponseEntity<ErrorMessage>
userNotFound(Exception ex, WebRequest request) {
var errors =
new ErrorMessage(404, new Date(),
ex.getMessage(), "User Not Found");
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:
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.util.Date;
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(UserNotFound.class)
public ResponseEntity<ErrorMessage>
userNotFound(Exception ex, WebRequest request) {
var errors =
new ErrorMessage(404, new Date(),
ex.getMessage(), "User Not Found");
return new ResponseEntity<>
(errors, HttpStatus.NOT_FOUND);
}
}
Custom Exception - UserNotFound
package com.knf.dev.demo.exception;
public class UserNotFound extends RuntimeException {
private static final long serialVersionUID = 1L;
public UserNotFound(String msg) {
super(msg);
}
}
package com.knf.dev.demo.exception;
public class UserNotFound extends RuntimeException {
private static final long serialVersionUID = 1L;
public UserNotFound(String msg) {
super(msg);
}
}
Error Message
package com.knf.dev.demo.exception;
import java.util.Date;
public record ErrorMessage
(Integer statusCode,
Date timestamp,
String message,
String description) {
}
package com.knf.dev.demo.exception;
import java.util.Date;
public record ErrorMessage
(Integer statusCode,
Date timestamp,
String message,
String description) {
}
User Rest Controller
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.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.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@Autowired
UserRepository userRepository;
//Create user
@PostMapping
public ResponseEntity<String> save(@RequestBody User usr) {
var userId = userRepository.saveAndReturnId(usr);
return new ResponseEntity<String>
("User successfully created , Id ="
+ userId, HttpStatus.CREATED);
}
//Get all users
@GetMapping
public ResponseEntity<List<User>> getAll() {
return new ResponseEntity<List<User>>
(userRepository.findAll(), HttpStatus.OK);
}
//Get user by id
@GetMapping("/{id}")
public ResponseEntity<User> getById
(@PathVariable("id") Long id) {
return new ResponseEntity<>
(userRepository.findOne(id), HttpStatus.OK);
}
// Delete user
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteUser
(@PathVariable("id") Long id) {
userRepository.delete(userRepository.findOne(id).id());
return new ResponseEntity<>
("User removed successfully", HttpStatus.OK);
}
// Update user
@PutMapping("/{id}")
public ResponseEntity<String> updateUser
(@PathVariable("id") Long id, @RequestBody User user) {
var _user = userRepository.findOne(id);
var _upUSer = new User(_user.id(),
user.firstName(),user.lastName(),user.email());
userRepository.update(_upUSer);
return new ResponseEntity<>
("Updated successfully", HttpStatus.OK);
}
}
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.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.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/v1/users")
public class UserController {
@Autowired
UserRepository userRepository;
//Create user
@PostMapping
public ResponseEntity<String> save(@RequestBody User usr) {
var userId = userRepository.saveAndReturnId(usr);
return new ResponseEntity<String>
("User successfully created , Id ="
+ userId, HttpStatus.CREATED);
}
//Get all users
@GetMapping
public ResponseEntity<List<User>> getAll() {
return new ResponseEntity<List<User>>
(userRepository.findAll(), HttpStatus.OK);
}
//Get user by id
@GetMapping("/{id}")
public ResponseEntity<User> getById
(@PathVariable("id") Long id) {
return new ResponseEntity<>
(userRepository.findOne(id), HttpStatus.OK);
}
// Delete user
@DeleteMapping("/{id}")
public ResponseEntity<String> deleteUser
(@PathVariable("id") Long id) {
userRepository.delete(userRepository.findOne(id).id());
return new ResponseEntity<>
("User removed successfully", HttpStatus.OK);
}
// Update user
@PutMapping("/{id}")
public ResponseEntity<String> updateUser
(@PathVariable("id") Long id, @RequestBody User user) {
var _user = userRepository.findOne(id);
var _upUSer = new User(_user.id(),
user.firstName(),user.lastName(),user.email());
userRepository.update(_upUSer);
return new ResponseEntity<>
("Updated successfully", HttpStatus.OK);
}
}
Spring Boot main driver
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);
}
}
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);
}
}
Step 4: Local Setup and Run the application
Step 1: Download or clone the source code from GitHub to the local machine - Click here
Step 2: mvn clean install
Step 3: Run the Spring Boot application -
mvn spring-boot:run
Step 1: Download or clone the source code from GitHub to the local machine - Click here
Step 2: mvn clean install
Step 3: Run the Spring Boot application -
mvn spring-boot:run