Spring Boot JPA example with PostgreSQL

In this tutorial we will learn how to create a basic JPA Spring Boot application that uses PostgreSQL as database. We will include a REST Controller and the Thymeleaf engine so that you can test your JPA Application in both ways.

Setting up the Project

Firstly, we will kickstart the Spring Boot project. The simplest way to do that is by means of the Spring Initializr online application.

Add the following dependencies, as you can see from the following picture:

  • Spring Data JPA
  • Validation
  • Postgres Database ( or the Database you are using)
  • Spring Web
  • Thymeleaf template engine

spring boot jpa example postgres

Alternatively, if you are using the Spring CLI, you can init the project as follows:

$ 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. 

Finally, import the project in your IDE before we move to the next steps. You can read more about Spring Boot IDE here: Choosing the IDE for Spring Boot development

Defining the Entity Class

An entity in JPA represents a table in the database. To create an entity, we need to create a Java class and annotate it with @Entity. Each field in the class represents a column in the table. The following code defines an entity called Customer with three fields: id, name, surname and email:

@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;

    // Getter/ Setters omitted for brevity

}

The Customer class is annotated with @Entity, which indicates that it is a JPA entity.

It has four fields: id, name, surname, and email.

The id field is annotated with @Id and @GeneratedValue, which indicates that it is the primary key of the entity and its value will be generated automatically.

The name, surname, and email fields are annotated with @NotBlank, which means that they cannot be empty.

When you let Hibernate auto-generate the Class, you will end up with the following Table in the PostgreSQL schema:

jpa spring boot postgresql example

Creating the repository

The repository is a layer between the database and the application that provides an interface for performing CRUD (create, read, update, delete) operations on the Person entity. To create a repository, we need to create an interface that extends the JpaRepository interface. The JpaRepository interface provides methods for performing basic CRUD operations and pagination.

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

As you can see from the above code, we are including only an extra method findByName to search a Customer by surname.

Creating the Controller

The controller is responsible for handling incoming HTTP requests and returning the appropriate response.

This controller defines several endpoints for performing CRUD operations on Person objects. The @RestController annotation indicates that this class is a controller and the @RequestMapping annotation specifies the base path for the endpoints defined in the class

@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.

Finally, we will add a Spring Boot view so that you can manage the application through the browser as well. We will build the view using the Thymeleaf engine.

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>

The Spring Boot Application Class

Finally, make sure your application includes a SpringBootApplication Class to start 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);
  }
}

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:

spring boot postgresql example

Add a new Customer:

spring boot postgresql jpa

Customer list with Edit/Delete:

spring boot postgresql jpa example

Conclusion

This article was a walkthrough Spring Boot and JPA using PostgreSQL as database.

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

Found the article helpful? if so please follow us on Socials
Twitter Icon       Facebook Icon       LinkedIn Icon       Mastodon Icon