Spring Boot JPA application with PostgreSQL

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.

Bootstrapping the Spring Boot application

Firstly, we will 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,validation, 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.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-validation</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>
Important: Until Spring Boot version 2.2 the starter spring-boot-starter-web had as dependency the starter spring-boot-starter-validation. In Spring Boot 2.3 the starter spring-boot-starter-validation is NOT a dependency of the starter spring-boot-starter-web anymore so you need to add it explicitly. 

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);
  }
}

We will now add some Classes to our project. For this purpose, we recommend importing the project in your IDE. You can read more about Spring Boot IDE here: Choosing the IDE for Spring Boot development

Next, 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. Finally, 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);
}

Building the Spring Boot view

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

If you prefer, you can have your Maven project to start the PostgreSQL Database for you automaticall, by including the fabric8-docker-plugin in your pom,xml:

<?xml version="1.0" encoding="UTF-8"?><plugin>
       
   <groupId>io.fabric8</groupId>
       
   <artifactId>docker-maven-plugin</artifactId>
       
   <version>0.33.0</version>
       
   <configuration>
             
      <skip>false</skip>
             
      <images>
                   
         <image>
                         
            <name>postgres</name>
                         
            <alias>postgresql</alias>
                         
            <run>
                               
               <env>
                                     
                  <POSTGRES_USER>postgres</POSTGRES_USER>
                                     
                  <POSTGRES_PASSWORD>postgres</POSTGRES_PASSWORD>
                                     
                  <POSTGRES_DB>postgres</POSTGRES_DB>
                                  
               </env>
                               
               <ports>
                                     
                  <port>5432:5432</port>
                                  
               </ports>
                               
               <log>
                                     
                  <prefix>PostgreSQL:</prefix>
                                     
                  <date>default</date>
                                     
                  <color>cyan</color>
                                  
               </log>
                               
               <wait>
                                     
                  <tcp>
                                           
                     <mode>mapped</mode>
                                           
                     <ports>
                                                 
                        <port>5432</port>
                                              
                     </ports>
                                        
                  </tcp>
                                     
                  <time>10000</time>
                                  
               </wait>
                            
            </run>
                      
         </image>
                
      </images>
          
   </configuration>
       
   <executions>
             
      <execution>
                   
         <id>docker-start</id>
                   
         <phase>test-compile</phase>
                   
         <goals>
                         
            <goal>stop</goal>
                         
            <goal>start</goal>
                      
         </goals>
                
      </execution>
             
      <execution>
                   
         <id>docker-stop</id>
                   
         <phase>post-integration-test</phase>
                   
         <goals>
                         
            <goal>stop</goal>
                      
         </goals>
                
      </execution>
          
   </executions>
    
</plugin>

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

$ mvn install spring-boot:run 

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

Home Page:

Add a new Customer:

Customer list with Edit/Delete:

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