Enterprise Java

Spring Data JPA with central exception handling and VO validations – framework

1. Introduction

Over the time, Spring framework became the de-facto standard to create any applications that are REST API based. Spring offers a variety of out of the box components to avoid writing the repeated and cumbersome boiler plate code. Also, the beautiful thing about Spring is that if there is a ready solution; it offers you the effortless ways to integrate with that available library/framework. In this article let’s see how to write a Spring based RESTful API’s using a full stack spring technology; Spring Boot, Spring Validations and Spring data JPA with an example that shows a complete information on the following:

  • Spring Boot and it’s configuration
  • Dependency Management with Spring boot starters
  • Avoiding bottleneck DAO code using Spring data JPA.
  • Spring’s support for validations at VO level.
  • Centralized exception handling.

We use the Gradle for dependency management and as the build tool. Let’s go through the steps.

2. Generate the project

Here are the steps that needed to be followed to generate the project.

2.1 Spring Intializer

Spring provides an easy to start project generation tool at this location Spring INITIALIZR. At this web page you can boot strap your application by adding the required dependencies. You can generate the project skeleton by adding the 3 dependencies mentioned below (please see the image below for clear understanding).
1. ‘Web’: This dependency is needed to code the web layer and to create the APIs. When the project gets generated, it shows up as the following dependency in the build.gralde file.
compile('org.springframework.boot:spring-boot-starter-web')
2. ‘Validation’ : To enable the spring validation. It shows up as the following dependency in the build.gradle.compile('org.springframework.boot:spring-boot-starter-validation')
3. ‘JPA’ : To enable the usage of spring data JPA. It shows up as the following dependency in the build.gradle.
compile('org.springframework.boot:spring-boot-starter-data-jpa')

Spring data jpa - Spring Initializer
Spring Initializer

2.2 Eclipse configuration

Generate the project and import it to eclipse. Once this is done, you are all set to create your API. The project imported in the eclipse should look like the below.

Spring data jpa - Project Structure
Project Structure

3. Create the API

Before writing the API, let’s create the packages as per the Java conventions as shown below.

Spring data jpa - java packages
java packages

With the generated code, we get a class at the root package i.e. com.example.spring.springrestapp. This is our bootup class.

Spring Data JPA - Boot-up class
Boot-up class

Note: Startup classes should be created at the root package level with the default configurations.
Now let’s go ahead and create a class for controller and an API method to add user details to DB. For this API which we are going to build, let’s assume some constraints as our requirements:

  • This API should collect First name, Last name, email, address and phone number of a user and store them in MySQL DB
  • API callers to pass First name, last name and email as mandatory fields. Email should be validated for its format.
  • Home address and phone number can be optional.

4. Configure DB details:

For this project, you need to have a local instance of MySQL DB running. You can provide the DB details in application.properties as below.

APPLICATION PROPERTIES

spring.datasource.url = jdbc:mysql://localhost:3306/boot_app
spring.datasource.username = root
spring.datasource.password = root

In the MySQL DB, lets create a table containing first name, last name, email, address, and phone number in the MySQL DB using the following script.

CREATE TABLE SCRIPT

create table user_info(
user_id smallint(10) primary key auto_increment,
first_name varchar(150),
last_name varchar(150),
email varchar(200),
address varchar(250),
phone smallint(10)
);

5. Configure the Spring Data JPA

Once table is ready, we need to map it a Java object using the JPA. Every field of the table is mapped in the java object using the annotations. Below is how our entity is going to look.

JPA ENTITY

package com.example.spring.springrestapp.dao.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;

import org.springframework.data.annotation.Id;

@Entity
@Table(name = "user_info")
public class UserInformation {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private Integer userId;

    @Column(name = "FIRST_NAME")
    private String firstName;
 
    @Column(name = "LAST_NAME")
    private String lastName;
 
   @Column(name = "EMAIL")
    private String email;
 
    @Column(name = "ADDRESS")
    private String address;
 
    @Column(name = "PHONE")
    private Integer phone;



	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 getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public Integer getPhone() {
		return phone;
	}

	public void setPhone(Integer phone) {
		this.phone = phone;
	}

	public String getEmail() {
		return email;
	}

	public void setEmail(String email) {
		this.email = email;
	}
}

Now, create the Spring data JPA repository. Data JPA repositories can be created by extending the interface JpaRepository as below. (Note that you need to pass the entity and data type of the ID field. In my example, entity is User and type of id field is Integer)

JPA REPOSITORY

package com.example.spring.springrestapp.dao.repo;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.spring.springrestapp.dao.entity.UserInformation;
public interface UserRepo extends JpaRepository {

}

That simple, our DAO code is ready! Spring takes care of generating the underlying DAO implementation.

6. Service and Controller layer

Let’s now create a service class just to save the User details. You can add the business logic as per your needs in the methods.

SERVICE CLASS

@Service
public class UserDetailService {
	@Autowired
	private UserRepo userRepo;
	
	public UserInformation saveUser(UserInformation user) {
		return userRepo.save(user);
	}
}

Let’s now go and create a controller and API method. The saveUser api accepts the json data in request body and returns the response as JSON in body.

CONTROLLER LAYER

@RestController
@RequestMapping("/api/user")
public class SpringRestAppController {

	@Autowired
	private UserDetailService userService;

	@PostMapping(value = "/save")
	public @ResponseBody UserInformation createUser(@RequestBody UserInformation user) {
		return userService.saveUser(user);
	}
}

@RequestMapping is used for mapping the resources.
@PostMapping is same as HttpPost assigned to @RequestMapping.

7. Configuring VO level validations

Our API needs validations on the data it receives as per the requirements mentioned in the beginning. For that, we are going to apply data validations at entity level as shown below.

DATA VALIDATIONS

@Entity
@Table(name = "user_info")
public class UserInformation {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private Integer userId;

    @Column(name = "FIRST_NAME")
    @NotBlank(message = "first name can't be blank")
    private String firstName;
 
    @Column(name = "LAST_NAME")
    @NotBlank(message = "last name can't be blank")
    private String lastName;
 
   @Column(name = "EMAIL")
   @NotBlank(message = "email can't be blank")
   @Email(message = "invalid format")
    private String email;
 
    @Column(name = "ADDRESS")
    private String address;
 
    @Column(name = "PHONE")
    private Integer phone;

Note that the annotation @NotBlank is to not allow empty or null values and @Email to check a valid email format. Also, we added messages for the failed validations.

Now, we need to tell Spring to do validations as per the annotations specified in the entity. For that, we can use @Valid annotation on request payload as below.

    @PostMapping(value = "/save")
    public @ResponseBody User createUser(@RequestBody @Valid UserInformation user) {
        return userService.saveUser(user);
    }

8. Configure exception Handling

When a validation is failed, we need to give the properly formatted error response to the API consumers. For example, if first name is not given, I want to return error message in below format with HTTP error code bad request. Its’s not good idea to give exception stack trace for the API consumers.

{
"errorCode": "VALIDATION_FAILED",
"message": ""
}

We can do it in each API controller method or we can create a single global exception handling, thus avoid writing duplicate code in multiple places for the same need.
To handle the central exception in a single place, we take advantage of Spring’s error handler. With Spring 3.2, @ControllerAdvice is provided to globalize the exception/error handling. To return error response lets create a VO having error Code and message.

ERROR HANDLING IN VALUE OBJECT

@JsonInclude(content = Include.NON_NULL)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ApiErrorVO {

	private String errorCode;
	private String message;

	public ApiErrorVO(String errorCode, String message) {
		super();
		this.errorCode = errorCode;
		this.message = message;
	}

When a validation fails, Spring throws a MethodArgumentNotValidException. We can catch this exception and extract the error message from the thrown exception. We use @ExceptionHandler to catch the exception as below.

EXCEPTION HANDLING

@ControllerAdvice
public class ApiExceptionHandler {

	@ExceptionHandler(MethodArgumentNotValidException.class)
	@ResponseStatus(code = HttpStatus.BAD_REQUEST)
	@ResponseBody
	public ApiErrorVO handleValidationError(MethodArgumentNotValidException ex) {
		BindingResult bindingResult = ex.getBindingResult();
		FieldError fieldError = bindingResult.getFieldError();
		String defaultMessage = fieldError.getDefaultMessage();
		return new ApiErrorVO("VALIDATION_FAILED", defaultMessage);
	}
}

@ResponseStatus is used to specify the HTTP Bad request status.
@ResponseBody makes sure error is written to response body.

9. Conclusion

Now let’s test the API.

Case 1: validation failure
URL: http://localhost:8080/restApp/api/user/save
RequestPayload: Notice the blank first name

{
"firstName":"",
"lastName":"K",
"email":"alexk@abc.com"
}

Response: with Http Status 400

{
"errorCode": "VALIDATION_FAILED",
"message": "first name can't be blank"
}

Case 2: When provided all required values
Request payload:

{
"firstName":"Alex",
"lastName":"K",
"email":"alexk@abc.com"
}

Response: With Http Status 200

{
"userId": 8,
"firstName": "Alex",
"lastName": "K",
"email": "alexk@abc.com",
"address": null,
"phoneNumber": null
}

This concludes the test.

10. Download the Source Code

Download
You can download the full source code here: SPRING DATA JPA

Chaitanya Rudrabhatla

Chaitanya K. Rudrabhatla works as a solutions architect in the Media and Entertainment domain. He has a proven ability to design and build complex web applications from ground up. Outside of work, he is an avid reader and a technology enthusiast who likes to be up to date with all the latest happenings in the Information technology world.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Igor Chistruga
Igor Chistruga
5 years ago

Any tips how to handle exceptions on database connection level?

4 years ago

Good Explanation.

4 years ago

this concept was very helpful.

Back to top button