In this tutorial we will learn how to create a basic CRUD Spring Boot application that uses PostgreSQL as database. We will be using Spring Boot CLI, then we will import the application into an IDE to create the full CRUD application.

We wil start from Spring Command Line Interface. You can however surf to the Spring Initializr Web application if you prefer.

Our Web application will require the following dependencies as specified by spring init:

$ spring init -dweb,data-jpa,postgresql,thymeleaf spring-crud-postgres

What is Thymeleaf? In a nutshell, Thymeleaf is an XML/XHTML/HTML5 template engine written in Java which is able to apply a set of transformations to template files in order to display data and/or text produced by your applications.

The following dependencies will be added to your pom.xml file:

<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-thymeleaf</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>
</dependencies>

Right now we have just a DemoApplication to bootstrap the project:

package com.example.cruddemo;

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

@SpringBootApplication 
public class DemoApplication {

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

Let's add our Entity object Customer.java:

package com.example.cruddemo.entities;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.NotBlank;

@Entity
public class Customer {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    @NotBlank(message = "Name is required")
    private String name;

    @NotBlank(message = "Surname is required")
    private String surname;

    @NotBlank(message = "Email is required")
    private String email;

    public Customer() {}

    public Customer(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public void setId(long id) {
        this.id = id;
    }
    
    public long getId() {
        return id;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setEmail(String email) {
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public String getEmail() {
        return email;
    }
    public String getSurname() {
        return surname;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

}

As you can see, our Entity class is pretty simple and contains some Validation constraints to ensure you don't enter blank data in the fields. Now let's add the core part of it, the CustomerController:

package com.example.cruddemo.controllers;

import javax.validation.Valid;

import com.example.cruddemo.entities.Customer;
import com.example.cruddemo.repositories.CustomerRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class CustomerController {
    
    private final CustomerRepository customerRepository;

    @Autowired
    public CustomerController(CustomerRepository customerRepository) {
        this.customerRepository = customerRepository;
    }
    
    @GetMapping("/new")
    public String showSignUpForm(Customer customer) {
        return "add-customer";
    }
    

    @GetMapping("/edit/{id}")
    public String showUpdateForm(@PathVariable("id") long id, Model model) {
        Customer customer = customerRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Invalid customer Id:" + id));
        model.addAttribute("customer", customer);
        return "update-customer";
    }

    @GetMapping("/delete/{id}")
    public String deleteUser(@PathVariable("id") long id, Model model) {
        Customer customer = customerRepository.findById(id).orElseThrow(() -> new IllegalArgumentException("Invalid customer Id:" + id));
        customerRepository.delete(customer);
        model.addAttribute("customers", customerRepository.findAll());
        return "index";
    }

    @PostMapping("/addcustomer")
    public String addUser(@Valid Customer customer, BindingResult result, Model model) {
        if (result.hasErrors()) {
            return "add-customer";
        }

        customerRepository.save(customer);
        model.addAttribute("customers", customerRepository.findAll());

        return "index";
    }

    @PostMapping("/update/{id}")
    public String updateUser(@PathVariable("id") long id, @Valid Customer customer, BindingResult result, Model model) {
        if (result.hasErrors()) {
            customer.setId(id);
            return "update-customer";
        }
        
        customerRepository.save(customer);
        model.addAttribute("customers", customerRepository.findAll());
        return "index";
    }
    

}

As you can see, this Controller class manages all aspects of the Entity through REST mappings. We will only add a Repository class to include a method to findByName the Customers:

package com.example.cruddemo.repositories;

import com.example.cruddemo.entities.Customer;

import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
    
    List<Customer> findByName(String name);
    
}

That's all. We will now add the HTML pages using the Thymeleaf engine. First off, the index.html page which list the existing Customers:

<body>
        <div th:switch="${customers}">
            <div class="row">
                <div>
                    <h2 th:case="null">No Customers.</h2>
                    <div th:case="*">
                        <h2>Customers</h2>
                        <table border="1">
                            <thead>
                                <tr>
                                    <th>Name</th>
                                    <th>Surname</th>
                                    <th>Email</th>
                                    <th>Edit</th>
                                    <th>Delete</th>
                                </tr>
                            </thead>
                            <tbody>
                                <tr th:each="customer : ${customers}">
                                    <td th:text="${customer.name}"></td>
                                    <td th:text="${customer.surname}"></td>
                                    <td th:text="${customer.email}"></td>
                                    <td><a th:href="@{/edit/{id}(id=${customer.id})}">EDIT</a></td>
                                    <td><a th:href="@{/delete/{id}(id=${customer.id})}">DELETE</a></td>
                                </tr>
                            </tbody>
                        </table>
                    </div>      
                    <p><a href="/new">Add Customer</a></p>
                </div>
            </div>
        </div>
    </body>

And now two pages, the add-customer.html page to insert a new Customer:

 <body>
        <div>
            <h2>New Customer</h2>
            <div>
                <div>
                    <form action="#" th:action="@{/addcustomer}" th:object="${customer}" method="post">
                        <div class="row">
                            <div >
                                <label for="name">Name</label>
                                <input type="text" th:field="*{name}" id="name" placeholder="Name">
                                <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" ></span>
                            </div>
                            <div >
                                <label for="surname">Surname</label>
                                <input type="text" th:field="*{surname}"  id="surname" placeholder="Surname">
                                <span th:if="${#fields.hasErrors('surname')}" th:errors="*{surname}" ></span>
                            </div>
                            <div >
                                <label for="email">Email</label>
                                <input type="text" th:field="*{email}"  id="email" placeholder="Email">
                                <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}" ></span>
                            </div>
                        </div>
                        <div class="row">
                            <div >
                                <input type="submit"  value="Add Customer">
                            </div>
                        </div>    
                    </form>
                </div>
            </div>
        </div>
    </body>

And the update-customer.html to Edit an existing customer:

    <body>
        <div>
            <h2>Update Customer</h2>
            <div class="row">
                <div>
                    <form action="#" th:action="@{/update/{id}(id=${customer.id})}" th:object="${customer}" method="post">
                        <div class="row">
                            <div>
                                <label for="name" >Name</label>
                                <input type="text" th:field="*{name}"  id="name" placeholder="Name">
                                <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}" ></span>
                            </div>
                            <div>
                                <label for="surname" >Surname</label>
                                <input type="text" th:field="*{surname}"  id="surname" placeholder="Name">
                                <span th:if="${#fields.hasErrors('surname')}" th:errors="*{surname}" ></span>
                            </div>
                            <div>
                                <label for="email" >Email</label>
                                <input type="text" th:field="*{email}"  id="email" placeholder="Email">
                                <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}" ></span>
                            </div>
                        </div>
                        <div class="row">
                            <div class="col-md-6 mt-5">
                                <input type="submit" class="btn btn-primary" value="Update Customer">
                            </div>
                        </div>    
                    </form>
                </div>
            </div>
        </div>
    </body>

Configuring Spring Boot to use PostgreSQL

Done with the application, let's add the configuration needed to configure postgresql connectivity into application.properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres

#drop n create table again, good for testing, comment this in production
spring.jpa.hibernate.ddl-auto=create

spring.jpa.show-sql=true
spring.jpa.database=POSTGRESQL
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

We have assumed that PostgreSQL is running on localhost on the default port. You can start an instance of PostgreSQL with docker as follows:

docker run --rm=true --name spring_boot_demo -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=postgres -p 5432:5432 postgres:10.5

Now compile your application then you can run it as usual with java -jar or with:

$ mvn spring-boot:run

And here's our application in action (graphic really essential!)

Home Page:

spring boot crud tutorial h2

Add a new Customer:

spring boot crud tutorial h2

Customer list with Edit/Delete:

spring boot crud tutorial h2

You can find the source code for this article here: https://github.com/fmarchioni/masterspringboot/tree/master/crud/spring-crud-postgres/cruddemo

FREE WildFly Application Server - JBoss - Quarkus - Drools Tutorials