Creating RESTful CRUD API with Spring Boot, PostgreSQL, JPA

In this article we're going to create Customer Management System. We'll build CRUD Restful API's.

aa.png

I'll try to explain everything along the way. I'm using Ubuntu 18.04 and Intellij IDE. Please make sure needed tools installed. If Postresql is not installed you can install it from here for ubuntu: Postresql

I'll not go into install and setup details. Let's navigate to start.spring.io I'm going to use java 11, project maven and spring boot 2.4.3 versions. Let's start to add dependencies we need for this project. After we added Spring web, Spring data JPA, Postresql Driver and Spring Data Elastic Search, click to generate and download the zip file.

aaa.png

Now our project is created and let's open it in the IDE. When we first open it intellij will ask to import default scripts (if not installed might ask to download) choose import and let's inspect pom.xml file

kk.png

We see the dependencies we added while creating the project on spring inializr

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<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.postgresql</groupId>
   <artifactId>postgresql</artifactId>
   <scope>runtime</scope>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-test</artifactId>
   <scope>test</scope>
</dependency>

Before start, we're going to use Model,View, Controller (MVC) Software Architectural design, I know you're wondering now, What is MVC? There's many ways to implement MVC so,I'll explain itfrom my point of view. MVC,

  • Seperates application functionality and logic
  • organized programming
  • make easier to work especially with a team

Model

  • Responsible for getting and manipulating the data
  • Interacts with database
  • Communicates with the Controller
  • Can update the view

View

  • What the user sees at the end (UI)
  • Communicates with the controller
  • Consist of (usually) html,css

Controller

  • Receives input (view, url)
  • Process Requests(Get, Put,Delete,Post)
  • Gets data from the model update the view

First step is to configure PostgreSQL to use it as database source.Open application.properties file and add the following

spring.datasource.url=jdbc:postgresql://localhost:5432/customers
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
##Hibernate properties
#The Sql dialect makes hibernate generate better Sql for the chosen database
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.properties.hibernate.format_sql=true
server.error.include-message=always

Note: If you’re using ubuntu and if you’re going to use postgres for the first time you need to configure postgresql settings and set a password as postgres in order to connect to the database and make this code above work for you. I set my password as postgres Check the link: dataAdmin

Now let’s create database in the postgreSql database server. open command line and type:

sudo -u postgres psql

we are connected to postgres it’s a default one and create table named Customers. Enter: CREATE DATABASE customers; if you get result as CREATE DATABASE as the following then it’s successful.

ee.png

To connect to database we created in that case it’s customers we type: \c customers;

You are now connected to database “customers” as user “postgres”. customers=#

Now our database created. Next step is to define domain Entity (Customer.java) Create new package named model and then right click to model and create new class named Customer. Time to add some properties to our customer model. It’s simple POJO class. add the attributes and generate constructer then getter and setter, the last thing to generate equals method and let’s add the annoations

package com.cobanogluhasan.Customer.Management.System.model;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

//An entity represents a table stored in a database
@Entity
@Table
public class Customer {
    //generate unique id
    @Id
    @SequenceGenerator(
            name = "customer_sequence",
            sequenceName = "customer_sequence",
            allocationSize = 1
    )
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "customer_sequence"
    )
    private Long id;
    private String fullName;
    private String email;

    //OnetoMany annoations basically one customer can have many orders
    @OneToMany(mappedBy = "customer")
    private Set<Order> orders = new HashSet<>();

    public Customer() {
    }

    public Customer(String fullName, String email, Set<Order> orders) {
        this.fullName = fullName;
        this.email = email;
        this.orders = orders;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getFullName() {
        return fullName;
    }

    public void setFullName(String fullName) {
        this.fullName = fullName;
    }

    public String getEmail() {
        return email;
    }

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

    public Set<Order> getOrders() {
        return orders;
    }

    public void setOrders(Set<Order> orders) {
        this.orders = orders;
    }

    @Override
    public String toString() {
        return "Customer{" +
                "id=" + id +
                ", fullName='" + fullName + '\'' +
                ", email='" + email + '\'' +
                ", orders=" + orders +
                '}';
    }

}

The relationship between Customer and Order models is, OneToMany-ManyToOne Annoations. Basically, one customer can have one or many orders, many orders or one order owned by one customer.Now we can start implementing Order model which should be added in model package.

package com.cobanogluhasan.Customer.Management.System.model;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;


@Entity
@Table(name = "orders")
public class Order {
    @Id
    @SequenceGenerator(
            name = "customer_sequence",
            sequenceName = "order_sequence"
    )
    @GeneratedValue(
            strategy = GenerationType.SEQUENCE,
            generator = "order_sequence"
    )
    private long id;

    private String details;
    private int amount;

    //ManytoOne relationship. Orders or one order can be related to one customer
    @ManyToOne
    @JoinColumn(name = "customerId", nullable = false)
    private Customer customer;


    public Order() {
    }

    public Order(String details, int amount) {
        this.details = details;
        this.amount = amount;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }

    public int getAmount() {
        return amount;
    }

    public void setAmount(int amount) {
        this.amount = amount;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                ", details='" + details + '\'' +
                ", amount=" + amount +
                ", customer=" + customer +
                '}';
    }
}

Create new package and name it as Repository. Let’s implement interfaces named as CustomerRepository then OrderRepository and extend it to JpaRepository as the following

CustomerRepository

package com.cobanogluhasan.Customer.Management.System.repository;

import com.cobanogluhasan.Customer.Management.System.model.Customer;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository annotates classes at the persistence layer, which will act as a database repository
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
}

OrderRepository

package com.cobanogluhasan.Customer.Management.System.repository;

import com.cobanogluhasan.Customer.Management.System.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
}

We’re ready to create our controllers. Create new package name controller and we’ll create new class named CustomerController it’ll include the following mapping for requests => Get, Post, Put,Delete

CustomerController

package com.cobanogluhasan.Customer.Management.System.controller;

import com.cobanogluhasan.Customer.Management.System.exception.ResourceNotFoundException;
import com.cobanogluhasan.Customer.Management.System.model.Customer;
import com.cobanogluhasan.Customer.Management.System.repository.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping( "/api/v1/")
public class CustomerController {

    @Autowired
    private CustomerRepository customerRepository;

    //Http request. get all the customers
    @GetMapping("customers")
    public List<Customer> getCustomers() {
        return this.customerRepository.findAll();
    }

    //get customer by id
    @GetMapping("/customers/{id}")
    public ResponseEntity<Customer> getCustomerById(@PathVariable(value = "id") Long customerId)
     throws ResourceNotFoundException {
        Customer customer = customerRepository.findById(customerId)
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found for that id" + customerId));
        return ResponseEntity.ok().body(customer);
    }

    //save customer
    @PostMapping("customers")
    public Customer createCustomer(@RequestBody Customer customer) {
        return this.customerRepository.save(customer);
    }
    //update customer
    @PutMapping("/customers/{id}")
    public ResponseEntity<Customer> updateCustomer(
            @PathVariable(value = "id") Long customerId,
            @Validated @RequestBody Customer customerDetails) throws ResourceNotFoundException {
        Customer customer = customerRepository.findById(customerId)
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found for that id" + customerId));

        customer.setEmail(customerDetails.getEmail());
        customer.setFullName(customerDetails.getFullName());
        customer.setOrders(customerDetails.getOrders());

        return ResponseEntity.ok(this.customerRepository.save(customer));
    }

    //delete customer
    @DeleteMapping("/customers/{id}")
    public Map<String, Boolean> deleteCustomer(@PathVariable(value = "id") Long customerId) throws ResourceNotFoundException {
        Customer customer = customerRepository.findById(customerId)
                .orElseThrow(() -> new ResourceNotFoundException("Customer not found for that id" + customerId));

        this.customerRepository.delete(customer);

        Map<String, Boolean> response = new HashMap<>();
        response.put("deleted", Boolean.TRUE);
        return response;
    }
}

Similar way the OrderController

import com.cobanogluhasan.Customer.Management.System.exception.ResourceNotFoundException;
import com.cobanogluhasan.Customer.Management.System.model.Order;
import com.cobanogluhasan.Customer.Management.System.repository.OrderRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping( "/api/v1/")
public class OrderController {

    @Autowired
    private OrderRepository orderRepository;

    //get orders
    @GetMapping("orders")
    public List<Order> getOrders() {
        return this.orderRepository.findAll();
    }

    //get order by id
    @GetMapping("/orders/{id}")
    public ResponseEntity<Order> getOrderById(@PathVariable(value = "id") Long orderId)
     throws ResourceNotFoundException {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found for that id" + orderId));
        return ResponseEntity.ok().body(order);
    }

    //save order
    @PostMapping("orders")
    public Order createOrder(@RequestBody Order order) {
        return this.orderRepository.save(order);
    }

    //update order
    @PutMapping("/orders/{id}")
    public ResponseEntity<Order> updateOrder(
            @PathVariable(value = "id") Long orderId,
            @Validated @RequestBody Order orderDetails) throws ResourceNotFoundException {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found for that id" + orderId));

        order.setAmount(orderDetails.getAmount());
        order.setCustomer(orderDetails.getCustomer());
        order.setDetails(orderDetails.getDetails());

        return ResponseEntity.ok(this.orderRepository.save(order));
    }

    delete order
    @DeleteMapping("/orders/{id}")
    public Map<String, Boolean> deleteOrder(@PathVariable(value = "id") Long orderId) throws ResourceNotFoundException {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new ResourceNotFoundException("Order not found for that id" + orderId));

        this.orderRepository.delete(order);

        Map<String, Boolean> response = new HashMap<>();
        response.put("deleted", Boolean.TRUE);
        return response;
    }
}

now we can run the application. The application started successfully and if we navigate to http://localhost:8080/api/v1/customers we’ll see an empty array. and test it via postman or intellij. start postman and test as in the picture

aaaaa.png

Post request is succeed and if you refresh the page you’ll see there’s information as:

[{“id”:3,”fullName”:”hasan”,”email”:”hasan.cobanoglu1@outlook.com.tr”,”orders”:[]}

if we sent Delete request on http://localhost:8080/api/v1/customers/3 and refresh the page, it’s gone… I hope you enjoyed this writing. If you have any questions or would like to give feedback, feel free to reach out.

source code