Photo by benjamin lehman on Unsplash
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:
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.spring.datasource.driverClassName: Set the JDBC driver class name for H2, which is
org.h2.Driver
.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.spring.datasource.password: Set the password associated with the H2 database. In this example, I used
password
.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.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.