Thursday, August 17, 2023

Spring Boot + Data JPA + MySQL REST API CRUD Example

In this post Spring Data tutorial we got to know about Spring Data and how to use it. In this tutorial we'll create a Spring Boot REST API crud application using Spring Data JPA and MySQL database.


Technologies used

  • Java 17
  • Spring Boot 3.1.2 (Which uses Jakarta EE version so you will need to use the jakarta. * packages instead of javax. * packages.)
  • Spring 6.x
  • MySQL 8.x

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.1.2</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.netjstech</groupId>
  <artifactId>datademo</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>springdata-demo</name>
  <description>Spring Data Demo</description>
  <properties>
    <java.version>17</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-devtools</artifactId>
      <scope>runtime</scope>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</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>

In the configuration, Spring Boot version used is 3.1.2 so Spring Boot gets the dependencies which are supported by this version.

Since we are using Spring Data JPA so spring-boot-starter-data-jpa dependency is added that will get Spring Data, Hibernate and other jars required for JPA.

This is a web application so we add spring-boot-starter-web dependency, that adds the necessary dependencies required for creating a Spring web application.

mysql-connector-j dependecy adds the MySQL driver required for the application to connect to DB.

Database table

In the application we are going to have a REST API to create, update and delete user records so we’ll have a USER table in the DB.

CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT,
  `first_name` varchar(45) NOT NULL,
  `last_name` varchar(45) NOT NULL,
  `user_type` varchar(15) NOT NULL,
  `start_date` date DEFAULT NULL,
  PRIMARY KEY (`id`)
)

DB Configuration

By default Spring boot reads properties file from this location src/main/resources/application.properties or application.yml file. Create application.yml file at this location and provide the DB connection attributes like URL, driver class name, username, password and Hibernate related properties.

By default Tomcat listens at port 8080 if you want to change port number then provide server.port property otherwise not required.

Change the DB properties as per your database configuration. I have created a schema named netjs that's why url is- jdbc:mysql://localhost:3306/netjs

server:
  port: 8081
 
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/netjs
    username: DB_USER
    password: DB_PASSWORD
  jpa:
    properties:
      hibernate:
        sqldialect: org.hibernate.dialect.MySQLDialect
        showsql: true

With these values in properties file and the jars in classpath for Hibernate and MySQL, Spring Boot can automatically configure Hibernate as JPA Vendor and set the DataSource using the DB connection attributes defined in application.yml file.

Entity Class

In the application we’ll have a JPA entity class with fields that map to the columns in the DB table USER.

  • @Entity annotation marks this model class as an Entity.
  • @Table annotation with the table name specifies the table to which this model class is mapped.
  • @Id annotation specifies the primary key of the entity.
  • @GeneratedValue annotation specifies the primary key generation strategy which is autoincrement in this case, so you don’t need to send ID data from the application, DB will take care of adding the ID.
  • @Column annotation specifies the corresponding table column for the annotated field.

While creating classes, create appropriate packages and create classes in those packages that makes the project more readable, makes component scanning easy from the root package. For example I have put entity class in com.netjstech.datademo.dao.entity package, controller class in com.netjstech.datademo.controller package and so on.

User.java

package com.netjstech.datademo.dao.entity;

import java.time.LocalDate;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

@Entity
@Table(name="user")
public class User {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	@Column(name = "id")
	private Long userId;
	@Column(name="first_name")
	private String firstName;
	@Column(name="last_name")
	private String lastName;
	@Column(name="user_type")
	private String userType;
	@Column(name="start_date")
	private LocalDate startDate;

	
	public Long getUserId() {
		return userId;
	}
	public void setUserId(Long userId) {
		this.userId = userId;
	}
	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 getUserType() {
		return userType;
	}
	public void setUserType(String userType) {
		this.userType = userType;
	}
	public LocalDate getStartDate() {
		return startDate;
	}
	public void setStartDate(LocalDate startDate) {
		this.startDate = startDate;
	}
  
}

Spring Data JPA Repository interface

Since we are using Spring Data JPA so you just need to create an interface that extends JpaRepository interface.

JpaRepository is a JPA specific extension of Repository that adds JPA related functionalities like flushing, persistence context along with the CRUD methods inherited from CrudRepository.

package com.netjstech.datademo.dao.repository;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import com.netjstech.datademo.dao.entity.User;

public interface UserRepository extends JpaRepository<User, Long> {
  List<User> findUserByUserType(String userType);
}

Here we have created interface UserRepository extending JpaRepository which takes the Entity class and the type of the Id as the type arguments. In our example Entity class is User and type of the Id is Long so these are passed as type arguments.

This interface is the only thing you need to write as your data access code. You don't need to explicitly implement this interface and implement all the CRUD methods. Spring framework automatically implements the operations.

As you can see there is one method added in the interface findUserByUserType(), Spring framework will parse the method and create the implementation for this method too. You don't need to write any implementation or provide SQL query for it.

Controller Class

We’ll create a Controller class UserController.java which is annotated using the @RestController annotation specifying it is a RESTful web service.

package com.netjstech.datademo.controller;

import java.net.URI;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import com.netjstech.datademo.dao.entity.User;
import com.netjstech.datademo.service.UserService;

@RestController
@RequestMapping("/user")
public class UserController {
  @Autowired
  UserService userService;
  
  @PostMapping
  ResponseEntity<?> addUser(@RequestBody User user){
    try {
      User savedUser = userService.addUser(user);
      final URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").build()
                .expand(savedUser.getUserId()).toUri();

      return ResponseEntity.created(location).body(savedUser);
    }catch(Exception e) {
      return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Failed to save User" + e.getMessage());
    }
  }
  
  // Get user by id
  @GetMapping("/{id}")
  ResponseEntity<?> getUserById(@PathVariable("id") long userId){
    try {
      User user = userService.getUserById(userId);
      return ResponseEntity.ok(user);

    }catch(Exception e){
      return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Error: " + e.getMessage());
    }
  }
  
  // Get all users
  @GetMapping
  ResponseEntity<?> getUsers(){
    try {
      List<User> users = userService.getAllUsers();
      return ResponseEntity.ok(users);

    }catch(Exception e){
      return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error while fetching " + e.getMessage());
    }
  }
  
  // Update user data
  @PutMapping
  ResponseEntity<?> updateUser(@RequestBody User user){
    try {
      return ResponseEntity.ok(userService.updateUser(user));
    }catch(Exception e) {
      return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Error: " + e.getMessage());
    }
  }
  
  @DeleteMapping("/{id}")
  ResponseEntity<String> deleteUser(@PathVariable("id") long userId){
    try {
      userService.deleteUserById(userId);
      return ResponseEntity.ok("User with id " + userId + " successfully deleted");
    }catch(Exception e) {
      return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Error: " + e.getMessage());
    }
  }
  
  @GetMapping("/type")
  ResponseEntity<?> getUserByType(@RequestParam("type") String userType){
    try {
      System.out.println("userType " + userType);
      List<User> users = userService.getAllUsersByUserType(userType);
      if(users.isEmpty()) {
        return ResponseEntity.noContent().build();
      }
      return ResponseEntity.ok(users);
    }catch(Exception e) {
      return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Error: " + e.getMessage());
    }
  }
}

At the class level there is an annotation @RequestMapping("/user") which means any URL having /user at the end will come to this RestController. Controller class has a dependency on UserService which is injected automatically using the @Autowired annotation.

In the controller class there are following methods-

  1. addUser() annotated with @PostMapping is used to insert a user record. This method has User object as an argument which is annotated with @RequestBody annotation. User object which is passed as HttpRequest body is mapped to the model object by @RequestBody annotation which means this annotation provided automatic deserialization of the HttpRequest body onto a Java object.
  2. getUsers() annotated with @GetMapping is used to get all the user records.
  3. getUserById() annotated with @GetMapping("/{id}") is used to fetch specific user record. Id is specified as a path variable which means any URL having the form /user/1 will be intercepted by this method.
  4. updateUser() annotated with @PutMapping is used to update user record. Return value of the method is of type ResponseEntity and the appropriate HttpStatus code is returned from the method.
  5. deleteUser() annotated with @DeleteMapping is used to delete the User record whose Id is passed as a path variable.
  6. getUserByType() method is used to get users belonging to the passed user type. Here @RequestParam is used which means it will match query parameters in the URL. For example in the URL /user/type?type=Gold parameter after the question mark is the query parameter.

Service Class

In the controller class there is a dependency on UserService and controller class just intercepts the request and calls the corresponding service class method. You don’t write any logic in Controller class.

UserService interface

package com.netjstech.datademo.service;

import java.util.List;
import com.netjstech.datademo.dao.entity.User;

public interface UserService {
  User addUser(User user);
  User getUserById(long userId);
  User updateUser(User user);
  void deleteUserById(long userId);
  List<User> getAllUsers();
  List<User> getAllUsersByUserType(String userType);
}

UserServiceImpl class

From the service layer we’ll call the DAO layer methods. With Spring Data we only need a repository so we’ll call methods of repository from the service class.

To use the repository, UserRepository instance is autowired in the Service class.

package com.netjstech.datademo.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.netjstech.datademo.dao.entity.User;
import com.netjstech.datademo.dao.repository.UserRepository;


@Service
public class UserServiceImpl implements UserService {
  
  @Autowired
  UserRepository userRepository;
  
  @Override
  @Transactional
  public User addUser(User user) {
    return userRepository.save(user);
  }

  @Override
  public User getUserById(long userId) {
    User user = verifyUser(userId);
    return user;
  }

  @Override
  @Transactional
  public User updateUser(User user) {
    verifyUser(user.getUserId());
    // Save modified user data (id is also there so existing record is updated)
    return userRepository.save(user);
    
    
  }

  @Override
  public void deleteUserById(long userId) {
    verifyUser(userId);
    userRepository.deleteById(userId);
  }

  @Override
  public List<User> getAllUsers() {
    return userRepository.findAll();
  }

  @Override
  public List<User> getAllUsersByUserType(String userType) {
    return userRepository.findUserByUserType(userType);
    // TODO Auto-generated method stub
    //return null;
  }
  
  // Utitlity method to verify User
  User verifyUser(long userId){
    User user = userRepository.findById(userId)
         .orElseThrow(()-> new IllegalArgumentException("No User found for the given id - " + userId));
    return user;
  }

}

Spring Boot Application Class

Application class with the main method which is the entry point for Spring Boot application.

package com.netjstech.datademo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class SpringdataDemoApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringdataDemoApplication.class, args);
	}

}

Testing the application

Run the REST application by running the SpringdataDemoApplication.java class.

For testing the application we'll use the rest client Postman, ensure that you have Postman app installed.

Inserting User record.

  1. Select POST as request type.
  2. URL as http://localhost:8080/user/
  3. Select Body and provide user data
    {
      "firstName": "Kavin",
      "lastName": "Michael",
      "userType": "Silver",
      "startDate": "2018-08-20"
    }
    
  4. Specify content type as JSON.
  5. Click Send

In the response body you should see the status as “201 Created” and also the created user record. Note that the Id is also added in the returned record.

Spring Boot REST API example

Getting specific user record

  1. Select GET as request type.
  2. URL as http://localhost:8080/user/5 (5 is userId)
  3. Click Send
Spring Boot Spring Data JPA

Same way you can get all the users by Selecting GET as request type and URL as http://localhost:8080/user

Updating user record

  1. Select PUT as request type.
  2. URL as http://localhost:8080/user
  3. Select Body and provide data with UserId for the user that has to be updated.
    {
      "userId": 6,
      "firstName": "Kavin",
      "lastName": "Michael",
      "userType": "Gold",
      "startDate": "2018-08-20"
    }
    
  4. Click Send

In the returned data you can verify that the user_type is modified for user id 6. Same can also be verified in the database.

Deleting user record

  1. Select Delete as request type.
  2. URL as http://localhost:8080/user/6 to delete User with Id as 6.
  3. Click Send
Spring Boot REST Delete

If you give any id that doesn't exist then you should get response status as "404 Not Found" and response body as error message.

Getting user by user type

  1. Select Get as request type.
  2. URL http://localhost:8080/user/type?type=platinum to get all users having user type as platinum.
  3. Click Send

That's all for this topic Spring Boot + Data JPA + MySQL REST API CRUD Example. If you have any doubt or any suggestions to make please drop a comment. Thanks!

>>>Return to Spring Tutorial Page


Related Topics

  1. Spring Boot Spring Initializr
  2. Spring Boot StandAlone (Console Based) Application Example
  3. Spring Boot spring-boot-starter-parent
  4. Spring MVC JSON as Response Example
  5. Spring NamedParameterJdbcTemplate Insert, Update And Delete Example

You may also like-

  1. Spring MVC Form Example With Bean Validation
  2. Transaction Management in Spring
  3. Spring JdbcTemplate Insert, Update And Delete Example
  4. Spring WebFlux Example Using Functional Programming - Spring Web Reactive
  5. How to Convert ArrayList to Array in Java
  6. Check if Given String or Number is a Palindrome Java Program
  7. Type Casting in Java With Conversion Examples
  8. Named Tuple in Python

2 comments:

  1. Here you have accessed service from controller. But the flow should be controller-repository-service isn't it. Please explain.

    ReplyDelete
    Replies
    1. No, it's the other way round Controller-Service-Repository(DAO layer).. That way you keep your web layer (Controller) separate from DAO Layer. Service class is supposed to be a stand alone class in between having the business logic.

      Delete