H2 into Action

Mastering Spring Boot with H2 Database Integration

Spring Boot has emerged as a powerhouse for building robust and scalable applications. In this blog post, I'll take you through a step-by-step guide on utilizing H2 for your Spring Boot projects.

Why H2 Database?

Before we jump into the technical aspects, it's important to understand why H2 database is an excellent choice for integration with Spring Boot.

Sometimes, you just need to get a database up and running quickly for your projects. It could be for testing everything thoroughly or seamlessly integrating with Spring JPA. Furthermore, H2 can be a great choice, not just for these scenarios but also when you're working on smaller projects that don't require the hefty capabilities of databases like MariaDB or PostgreSQL.

Getting Started with Spring Boot and H2

In this section, I'll walk you through the process of setting up a Spring Boot project with H2 integration.

Dependencies

In this blog post, I'll demonstrate how to use H2 with Spring JPA. Below is the code you need to insert in your pom.xml file.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

Database Connection Setup

In my setup, I'll add these configurations to my application.properties file.

spring.datasource.url=jdbc:h2:file:./data/db
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

These properties will establish the connection between my Spring Boot application and the H2 database.

Here's a breakdown of the configuration properties and their purposes:

  1. spring.datasource.url: This property specifies the URL for your H2 database. In this example, I used a file-based H2 database located in the ./data/db directory.

  2. spring.datasource.driverClassName: Set the JDBC driver class name for H2, which is org.h2.Driver.

  3. spring.datasource.username: Specify the username to access the H2 database. In this case, I set it to sa , but you can use whatever you want.

  4. spring.datasource.password: Set the password associated with the H2 database. In this example, I used password.

  5. spring.jpa.hibernate.ddl-auto: This property controls how Hibernate handles database schema changes. Setting it to update allows To Hibernate to automatically update the database schema based on your entity classes.

  6. spring.jpa.database-platform: Specify the H2 dialect that Hibernate should use. org.hibernate.dialect.H2Dialect is the appropriate dialect for H2 databases.

Creating Data Models

In this step, I will show you how to create simple tables with Spring JPA for your H2 Database.

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;

import java.util.Set;

/**
 * created by Christian Lehnert
 *
 * This class represents the user table in the database.
 */
@Entity
@Table(name="users")
@NoArgsConstructor
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Setter
    @Getter
    private  Integer id;

    @Setter
    @Getter
    private String name;
    @Column(nullable = false, unique = true)
    @Setter
    @Getter
    private String userName;

    @Setter
    @Getter
    @Column(nullable = false, unique = true)
    private String email;

    @Setter
    @Getter
    @Column(nullable = false)
    private String password;
    @ManyToMany
    @LazyCollection(LazyCollectionOption.FALSE)
    private Set<Role> roles;

    public Set<Role> getRoles() {
        return roles;
    }
    public void setRoles(Set<Role> roles) {
        this.roles = roles;
    }
}
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

import java.util.UUID;


/**
 * Created by Christian Lehnert
 *
 * This class represents the role table in the database.
 */
@Entity
@NoArgsConstructor
@Table(name="roles")
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Setter
    @Getter
    private Integer id;

    @Setter
    @Getter
    @Column(unique = true)
    private String name;
}

I've created two classes, Role and User. These two classes can be used to store users and their roles. In this example, you can see that I have used Integer for the IDs, but you should use UUID in real-world examples for improved uniqueness and security.

I have also used Lombok in this code to eliminate the need to manually create setters and getters.

Repository and CRUD Operations

With your data models in place, it's time to delve into repository interfaces and perform CRUD (Create, Read, Update, Delete) operations.

package org.lehnert.ContactKeeper.db.repository;

import org.lehnert.ContactKeeper.db.tables.Role;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;
import java.util.UUID;

/**
 * Created by Christian Lehnert
 *
 * This interface is used to access the role table.
 */
public interface RoleRepository extends JpaRepository<Role, Integer> {
    Optional<Role> findByName(String name);
}
package org.lehnert.ContactKeeper.db.repository;

import org.lehnert.ContactKeeper.db.tables.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by Christian Lehnert
 *
 * This interface is used to access the user table.
 */
public interface UserRepository extends JpaRepository<User, Integer> {

    User findByUserNameOrEmail(String username, String email);

    boolean existsByUserName(String username);

    boolean existsByEmail(String email);
}

I've created two interfaces, RoleRepository and UserRepository, both of which extend JpaRepository to inherit the fundamental functionality provided by Spring JPA. Additionally, I've included custom methods in these interfaces to retrieve data based on specific queries, such as findByName.

Bringing It All Together

After creating a data model and all the necessary CRUD operations, I can utilize the repositories to perform real-world tasks using Spring boot controllers.

package org.lehnert.ContactKeeper.controller;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.lehnert.ContactKeeper.db.repository.RoleRepository;
import org.lehnert.ContactKeeper.db.repository.UserRepository;
import org.lehnert.ContactKeeper.db.tables.Role;
import org.lehnert.ContactKeeper.db.tables.User;
import org.lehnert.ContactKeeper.dto.SignUpDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Collections;

@Controller
public class AuthController {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private RoleRepository roleRepository;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @ResponseBody
    @PostMapping("/signup")
    public ResponseEntity<?> registerUser(@RequestBody SignUpDto signUpDto) {

        // checking for username exists in a database
        if (userRepository.existsByUserName(signUpDto.getUsername())) {
            return new ResponseEntity<>("Username is already exist!", HttpStatus.BAD_REQUEST);
        }
        // checking for email exists in a database
        if (userRepository.existsByEmail(signUpDto.getEmail())) {
            return new ResponseEntity<>("Email is already exist!", HttpStatus.BAD_REQUEST);
        }
        // creating user object
        User user = new User();
        user.setName(signUpDto.getName());
        user.setUserName(signUpDto.getUsername());
        user.setEmail(signUpDto.getEmail());
        user.setPassword(passwordEncoder.encode(signUpDto.getPassword()));
        Role roles = roleRepository.findByName("ROLE_ADMIN").get();
        user.setRoles(Collections.singleton(roles));
        userRepository.save(user);

        System.out.println("User is registered successfully!");
        System.out.println(user.getEmail());
        System.out.println(user.getPassword());
        System.out.println(user.getRoles());
        System.out.println(user.getUserName());

        return new ResponseEntity<>("User is registered successfully!", HttpStatus.OK);
    }
}

Here is a simple Spring Boot controller with a /signup endpoint. As you can see, I use the userRepository to check if a user already exists. If the user does exist, I return an error message. If the user does not exist, I create a new user with a secured password and then save it to the database.

Conclusion

H2 is an easy-to-use database for Spring Boot that allows you to effortlessly create a database without the need to install a local database or set up a Docker container. There is a lot more to discover and learn about H2.

Did you find this article valuable?

Support Christian Lehnert by becoming a sponsor. Any amount is appreciated!