Spring Boot H2 Database example application

In this tutorial we will learn how to create a basic Spring Boot application that uses H2 Database. We will provide at first an overview of how to use H2 DB with Spring Boot, then we will use the Spring Boot CLI to bootstrap an example CRUD application.

Overview of H2 Database

Firstly, H2 Database is a pure Java Database with JDBC and Transactional support. You can run it in two modes:

  • Embedded within a Java Process (for example a Spring Boot application)
  • As standalone application

You can also use either in-memory (volatile) storage or disk-based storage.

Spring Boot can auto-configure an embedded H2 with the following settings:

Driver Class org.h2.Driver
JDBC URL jdbc:h2:mem:testdb
User Name sa
Password <blank>

The only requirement is to include the following dependency and the embedded H2 Database will start:

<dependency>
    <groupId>org.hsqldb</groupId>
    <artifactId>hsqldb</artifactId>
    <scope>runtime</scope>
</dependency>

If you want to customize the Database settings, you can add them within your application.properties:

spring.datasource.url=jdbc:h2:mem:demodb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=secret
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

Let’s see how to build a complete Spring Boot CRUD application using H2 Database

Creating a CRUD Spring Boot application with H2 Database

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

Our Web application, named spring-crud-h2, will require the following dependencies as specified by spring init:

$ spring init -dweb,data-jpa,h2,validation,thymeleaf spring-crud-h2 
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. 

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:

<?xml version="1.0" encoding="UTF-8"?>
<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>com.h2database</groupId>
      <artifactId>h2</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);
  }
}

Next, we will 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. Next, 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. Finally, we will 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>

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:

h2 database spring boot example

Add a new Customer:

spring boot h2 database tutorial

Customer list with Edit/Delete:

spring boot h2 db tutorial

Connecting to the H2 Database Web Console

H2 DB includes a Web application to manage/view your Db structure. To enable the H2 Database Web application you need to add the following property in your application.properties:

spring.h2.console.enabled=true

Now you can connect to the console which is available at: http://localhost:8080/h2-console/

spring boot h2 database

Click Connect to enter in the selected DB Schema.

Source code

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

You can check the following tutorial to learn how to use PostgreSQL as database: How to create a Spring Boot Crud JPA application with PostgreSQL