Create CRUD REST API with H2 Database and JPA with Spring Boot in under 15 min

by Jun 11, 2022Cloud Native0 comments

In this tutorial, we are going to build a Spring Boot Rest CRUD API with Maven as our build tool. Rest APIs make it possible to establish communication between a backend server and a frontend web or mobile applications.

Prerequisites

To verify if Java and Java compiler are installed and configured correctly on your system, open the terminal and type in the command java --version to see the version of Java installed and javac --version to see the version of Java compiler installed.

$ java -version
openjdk 11.0.7 2020-04-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.7+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.7+10, mixed mode)
$ javac -version
javac 11.0.7

Rest API CRUD operations map to HTTP verbs and SQL operations as shown in the table below.

Operation SQL HTTP verb Rest web service
Create INSERT POST POST
Read SELECT GET GET
Update UPDATE PUT/PATCH PUT
Delete DELETE DELETE DELETE

Project setup

The easiest way to create a new spring boot application is to use the spring initializr.

  • Open spring initializr in your web browser.
  • Choose the Maven project.
  • Choose Java as the language.
  • Leave the default selected Spring Boot version.
  • Provide Group and Artifact Name with some Description
  • Select Java 8 as the Java Version.
  • In the dependencies section add Lombok, Spring Web, H2 Database, Spring Data JPA & Actuator as the dependencies.
  • Click on the generate button to download the project as a zip file.
  • Extract the zip file and open the project in your favorite IDE.
  • Sync the dependencies with Maven.

Configuring Spring Datasource, JPA, Hibernate

We are using the H2 database which is an in-memory database, meaning the data stored in the database is destroyed if the application is stopped or restarted.

In the resources folder within the src/main folder, open application.properties file and write the below properties.

spring.datasource.url=jdbc:h2:mem:todo
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
  • spring.datasource.username & spring.datasource.password properties are the H2 database username and password. The default H2 database username is sa and password is password.
  • Spring Boot uses Hibernate for Spring Data JPA implementation, that is why we configure spring.jpa.database-platform=org.hibernate.dialect.H2Dialect.
  • spring.datasource.url=jdbc:h2:mem:todo species the database url and the database name. In our case the database name is todo.

Todo model

Models are plain old Java objects that represent a table in the database. We will start by creating a model package in our root project package io.initializ.labs.todo.

Within the model package created above, create a Java enum with the name TodoStatus with the fields as shown below.

public enum TodoStatus {
    COMPLETED, NOT_COMPLETED
}

Within the model package created above, create a Java class with the name Todo with the fields as shown below.

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Builder;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.sql.Timestamp;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Todo {
    @Id
    @GeneratedValue
    @Column(updatable = false, nullable = false)
    Long id;
    @Column
    String title;
    @Column
    String description;
    @Column
    TodoStatus todoStatus;

    @CreationTimestamp
    @Column(updatable = false)
    Timestamp dateCreated;
    @UpdateTimestamp
    Timestamp lastModified;

}

  • @Entity annotation shows that the class is a persistent Java class.
  • @Id annotation shows that the annotated field is the primary key.
  • @GeneratedValue annotation is used to specify the generation strategy used for the primary key.
  • @Column annotation defines the column in the database that maps the annotated field.
  • @CreationTimestamp annotation is a JPA annotation that automatically updates the todo creation timestamp.
  • @UpdateTimestamp annotation is a JPA annotation that automatically updates the todo last modified timestamp.
  • @Data annotation is from project Lombok. It generates the getters and setters for all the fields that we have in the todo class, equals method, and a toString method.
  • @NoArgsConstructor annotation is from project Lombok and it generates an empty constructor for our Todo class.
  • @AllArgsConstructor annotation is from project Lombok and it generates a constructor with all the fields that are available in our Todo class.
  • @Builder annotation is from project Lombok. It makes it possible for us to use the builder pattern with our Todo model.

We will use the builder pattern later in the article when creating initial bootstrap data.

Creating the repository interface

In the root package of our project, create a package with the name repositories. In the repositories package created above, create an interface with the name TodoRepository that extends the CrudRepository interface and comes with CRUD functions already implemented.

import io.initializ.labs.todo.model.Todo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TodoRepository extends CrudRepository<Todo, Long> {
}

The CrudRespository interface takes in the model and the type of the ID, in our case the model is Todo and the ID type is Long. We are now able to use all the CrudRepository methods save(), findOne(), findById(), findAll(), count(), delete(), deleteById() without providing implementation.

 

  • @Repository annotation marks this interface as a Spring Data JPA repository.

Creating the todo service

A service is an interface from which different implementations of the same functions can be made.

In the root package of our application create a package with the name services. In the services package created above, create an interface with the name TodoService.

import java.util.List;

public interface TodoService {
    List<Todo> getTodos();

    Todo getTodoById(Long id);

    Todo insert(Todo todo);

    void updateTodo(Long id, Todo todo);

    void deleteTodo(Long todoId);
}

The interface above defines the base CRUD operations that we will implement in our TodoServiceImpl class. In the services package create a class with the name TodoServiceImp and implements the TodoService interface we created above.

import io.initializ.labs.todo.model.Todo;
import io.initializ.labs.todo.repositories.TodoRepository;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class TodoServiceImpl implements TodoService {
    TodoRepository todoRepository;

    public TodoServiceImpl(TodoRepository todoRepository) {
        this.todoRepository = todoRepository;
    }

    @Override
    public List<Todo> getTodos() {
        List<Todo> todos = new ArrayList<>();
        todoRepository.findAll().forEach(todos::add);
        return todos;
    }

    @Override
    public Todo getTodoById(Long id) {
        return todoRepository.findById(id).get();
    }

    @Override
    public Todo insert(Todo todo) {
        return todoRepository.save(todo);
    }

    @Override
    public void updateTodo(Long id, Todo todo) {
        Todo todoFromDb = todoRepository.findById(id).get();
        System.out.println(todoFromDb.toString());
        todoFromDb.setTodoStatus(todo.getTodoStatus());
        todoFromDb.setDescription(todo.getDescription());
        todoFromDb.setTitle(todo.getTitle());
        todoRepository.save(todoFromDb);
    }

    @Override
    public void deleteTodo(Long todoId) {
        todoRepository.deleteById(todoId);
    }
}

We will create and initialize the TodoRepository in the constructor of the class above to be able to use the various methods that CrudRepository provides.

  • @service annotation makes Spring context be aware of this class as a service.

Creating the Rest API controller

In the root package of our project create a package with the name controllers. In the controllers package we created above, create a Java class with the name TodoController.

import io.initializ.labs.todo.model.Todo;
import io.initializ.labs.todo.services.TodoService;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/api/v1/todo")
public class TodoController {
    TodoService todoService;

    public TodoController(TodoService todoService) {
        this.todoService = todoService;
    }

    //The function receives a GET request, processes it and gives back a list of Todo as a response.
    @GetMapping
    public ResponseEntity<List<Todo>> getAllTodos() {
        List<Todo> todos = todoService.getTodos();
        return new ResponseEntity<>(todos, HttpStatus.OK);
    }
    //The function receives a GET request, processes it, and gives back a list of Todo as a response.
    @GetMapping({"/{todoId}"})
    public ResponseEntity<Todo> getTodo(@PathVariable Long todoId) {
        return new ResponseEntity<>(todoService.getTodoById(todoId), HttpStatus.OK);
    }
    //The function receives a POST request, processes it, creates a new Todo and saves it to the database, and returns a resource link to the created todo.           @PostMapping
    public ResponseEntity<Todo> saveTodo(@RequestBody Todo todo) {
        Todo todo1 = todoService.insert(todo);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.add("todo", "/api/v1/todo/" + todo1.getId().toString());
        return new ResponseEntity<>(todo1, httpHeaders, HttpStatus.CREATED);
    }
    //The function receives a PUT request, updates the Todo with the specified Id and returns the updated Todo
    @PutMapping({"/{todoId}"})
    public ResponseEntity<Todo> updateTodo(@PathVariable("todoId") Long todoId, @RequestBody Todo todo) {
        todoService.updateTodo(todoId, todo);
        return new ResponseEntity<>(todoService.getTodoById(todoId), HttpStatus.OK);
    }
    //The function receives a DELETE request, deletes the Todo with the specified Id.
    @DeleteMapping({"/{todoId}"})
    public ResponseEntity<Todo> deleteTodo(@PathVariable("todoId") Long todoId) {
        todoService.deleteTodo(todoId);
        return new ResponseEntity<>(HttpStatus.NO_CONTENT);
    }
}

  • @RestController annotation marks this class as a controller that can process the incoming HTTP requests.
  • @RequestMapping("/api/v1/todo") annotation sets the base path to the resource endpoints in the controller as /api/v1/todo.
  • We inject the TodoService through our contractor to be able to use the various methods defined in it within the TodoController class.
  • @GetMapping annotation indicates that the function processes a GET request.
  • @PostMapping annotation indicates that a function processes a POST request.
  • @PutMapping annotation indicates that a function processes a PUT request.
  • @DeleteMapping annotation indicates that a function processes a DELETE request.

Creating a bootstrapper

Data bootstrapper creates and loads the initial data whenever the application runs. We will make use of the builder pattern we mention while creating the Todo model. In the root package of our project, create a package with the name bootstrap. In the bootstrap package created above create a Java class with the name TodoLoader.

import io.initializ.labs.todo.model.Todo;
import io.initializ.labs.todo.model.TodoStatus;
import io.initializ.labs.todo.repositories.TodoRepository;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class TodoLoader implements CommandLineRunner {
    public final TodoRepository todoRepository;

    public TodoLoader(TodoRepository todoRepository) {
        this.todoRepository = todoRepository;
    }

    @Override
    public void run(String... args) throws Exception {
        loadTodos();
    }

    private void loadTodos() {
        if (todoRepository.count() == 0) {
            todoRepository.save(
                    Todo.builder()
                            .title("Go to market")
                            .description("Buy eggs from market")
                            .todoStatus(TodoStatus.NOT_COMPLETED)
                            .build()
            );
            todoRepository.save(
                    Todo.builder()
                            .title("Go to school")
                            .description("Complete assignments")
                            .todoStatus(TodoStatus.NOT_COMPLETED)
                            .build()
            );
            System.out.println("Sample Todos Loaded");
        }
    }
}

  • @Component annotation informs Spring that this class is a Spring component.

Running the Application

From command line
Right click on the TodoApplication.java file and run as Java Application

From command line
Run mvn clean spring-boot:run

Testing the CRUD endpoints

You can download Postman and invoke the API endpoint.

Creating a new Todo

Make a POST request with the JSON body as shown below to http://127.0.0.1:8080/api/v1/todo.

{
    "title": "Go to market",
    "description": "Buy eggs from market",
    "todoStatus": "NOT_COMPLETED"
}

Getting the list of todos

Make a GET request to http://127.0.0.1:8080/api/v1/todo to get all the todos.

Getting a Todo by ID

Make a GET request to http://127.0.0.1:8080/api/v1/todo/2 specifying the ID of the Todo at the end of the URL, in our case ID is 2.

Updating a Todo

Make a PUT request to http://127.0.0.1:8080/api/v1/todo/2 adding the ID of the todo to update in the URL, in our case the ID is 2 and a JSON body with the fields to update.

{
    "title": "Market",
    "description": "Buy eggs from supermarket",
    "todoStatus": "NOT_COMPLETED"
}

Deleting a Todo

Make a DELETE request to http://127.0.0.1:8080/api/v1/todo/2 adding to the end of the URL the ID of the todo to delete, in our case the ID is 2.

Conclusion

Now you have learned how to create a Restful web service in Spring Boot in under 15 min