Overview

In this post, I will add H2 to a simple Spring Boot Application so that we can experience the amazingly simple configuration for JPA Repositories. The post will also show how to have a basic CRUD application with Spring Data JPA.

H2 is a light weight open source database which can be configured to run as in-memory database.

In case you missed, please refer below link for setting up a basic Spring Boot Sample REST API Application

Spring Boot Sample REST API Application

Getting Started

We shall start with the Spring Initializr, but this time will add more dependencies for JPA and H2:

Download the source in zip, unzip and import to IntelliJ

Your build.gradle should look like:

plugins {
	id 'org.springframework.boot' version '2.2.0.RELEASE'
	id 'io.spring.dependency-management' version '1.0.8.RELEASE'
	id 'java'
}

group = 'com.logicalsapien.sprintbooth2'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	runtimeOnly 'com.h2database:h2'
	testImplementation('org.springframework.boot:spring-boot-starter-test') {
		exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
	}
}

test {
	useJUnitPlatform()
}

Dry run

You can right away start the application, but it won’t do anything. So, lets just add a bit of code.

Add below property to src/main/resrouces/application.properties

# To Enable H2 Console
spring.h2.console.enabled=true

Then right click on Sprintbooth2Application.java and run the main() method to start the application.

The Application will start in port 8080.

We don’t have any application features added, but we have an in-memory H2 Database up. Lets check it out:

Goto : http://localhost:8080/h2-console in your browser; it’ll open the H2 console connection window.

Please ensure that, the jdbc url is jdbc:h2:mem:testdb. Then Connect. It’ll open up the H2 management console without any database in it.

Add an Entity class

Lets get to business. Add an entity class, Planet.java

package com.logicalsapien.sprintbooth2.entity;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

/**
 * Planet Entity Class.
 */
@Entity
public class Planet {

    /**
     * Id.
     */
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    private Long id;

    /**
     * Planet Name.
     */
    private String name;

    /**
     * No Of Moons.
     */
    private Integer numberOfMoons;
}
  • When we add a model class with @Entity annotation, Spring data will map that class to a table. A table with name planet will be created in db.
  • And and id field with @Id is mandatory
  • @GeneratedValue is to specify the column increment strategy. Default is Auto strategy. Here we specified strategy= GenerationType.IDENTITY which means, we have delegated the id creation to db server
  • Default Table columns are derived from class variables, unless more options are specified by optional @Column annotation

More H2 Configuration

Configure H2 to persist the data between server restarts

spring.datasource.url=jdbc:h2:file:/planetdb

You can make below changes as you wish

# To Enable H2 Console
spring.h2.console.enabled=true
# For DB persistence between server restarts
#This needs to be absolute path such as ./data/planetdb
spring.datasource.url=jdbc:h2:file:./data/planetdb
# For in memory db
#spring.datasource.url=jdbc:h2:mem:testdb
# H2 driver
spring.datasource.driverClassName=org.h2.Driver
# Default user name
spring.datasource.username=sa
# Default password
spring.datasource.password=
# JPA Platform
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

You can also add a data.sql file to intialize the db with some data. Here we will pre-populate the Planet DB:

insert into planet values(1, 'Mercury', 0);
insert into planet values(2, 'Venus', 0);
insert into planet values(3, 'Earth', 1);
insert into planet values(4, 'Mars', 2);
insert into planet values(5, 'Jupiter', 79);
insert into planet values(6, 'Saturn', 83);
insert into planet values(7, 'Uranus', 27);
insert into planet values(8, 'Neptune', 14);

Now start the application. After application is started, open the H2 Management console. Since we have changed the jdbc url, we need to update it in the H2 console login screen:

Now, you can see the db and data we have created there:

Spring Components:

Repository

Create the Repository Interface, PlanetRepository.java

package com.logicalsapien.sprintbooth2.repository;

import com.logicalsapien.sprintbooth2.entity.Planet;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface PlanetRepository extends CrudRepository<Planet, Long> {

    /**
     * Method to return List of Planets with same substring in name.
     * Does the same job as below method, but different way of doing in Spring
     * @param value Value to check
     * @return List of Planel
     */
    List<Planet> findByNameContaining(String value);

    /**
     * Method to return List of Planets with same substring in name.
     * Does the same job as above method, but different way of doing in Spring
     * @param value Value to check
     * @return List of Planel
     */
    @Query("SELECT p FROM Planet p WHERE p.name LIKE %:value%")
    List<Planet> findByNameLike(@Param("value") String value);
}
  • We extend the CrudRepository provided by Spring Data JPA. The framework then inspect the interface and create implementation under the hood. The CrudRepository gives us access to commonly used database queries such as find, save, delete etc.
  • If we want to have query based on field names, we can use interface method declarations to have spring create needed query implementations, like the first method above. Detailed documentation available here.
  • We can specify custom queries as well, as shown in the second method
  • Spring data provide more features such as auditing, paging etc.
  • Still not satisfied, we can have custom implementation along with Spring Implementation

Service Layer

We use Program to Interface Pattern to define the Service Layer

PlanetService.java

package com.logicalsapien.sprintbooth2.service;

import com.logicalsapien.sprintbooth2.entity.Planet;

import java.util.List;

/**
 * Planet Service.
 */
public interface PlanetService {

    /**
     * Get All Planets.
     * @return List of all planets.
     */
    List<Planet> getAllPlanets();

    /**
     * Get Planet By Id.
     * @param id Id
     * @return Planet
     */
    Planet getPlanetById(Long id);

    /**
     * Save Planet.
     * @param planet Planet to save
     * @return Saved Planet
     */
    Planet savePlanet(Planet planet);

    /**
     * Update Planet.
     * @param id Id
     * @param planetToUpdate Planet to Update
     * @return Updated Planet
     */
    Planet updatePlanetById(Long id, Planet planetToUpdate);

    /**
     * Delete Planet by Id.
     * @param id Id
     */
    void deletePlanetById(Long id);

    /**
     * Search Planet by name containing.
     * @param searchString SearchString
     * @return Search result
     */
    List<Planet> getPlanetByNameContaining(String searchString);

    /**
     * Search Planet by name like.
     * @param searchString SearchString
     * @return Search result
     */
    List<Planet> getPlanetByNameLike(String searchString);
}

PlanetServiceImpl.java

package com.logicalsapien.sprintbooth2.service;

import com.logicalsapien.sprintbooth2.entity.Planet;
import com.logicalsapien.sprintbooth2.repository.PlanetRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * Plane Service Implementation.
 */
@Service
public class PlanetServiceImpl implements PlanetService{

    /**
     * Autowiring the Planet Repository
     */
    @Autowired
    private PlanetRepository planetRepository;

    /**
     * Get All Planets.
     * @return List of all planets.
     */
    @Override
    public List<Planet> getAllPlanets() {
        return (List<Planet>) planetRepository.findAll();
    }

    /**
     * Get Planet By Id.
     * @param id Id
     * @return Planet
     */
    @Override
    public Planet getPlanetById(final Long id) {
        return planetRepository.findById(id).get();
    }

    /**
     * Save Planet.
     * @param planet Planet to save
     * @return Saved Planet
     */
    @Override
    public Planet savePlanet(final Planet planet) {
        return planetRepository.save(planet);
    }

    /**
     * Update Planet.
     * @param id Id
     * @param planetToUpdate Planet to Update
     * @return Updated Planet
     */
    @Override
    public Planet updatePlanetById(
            final Long id, final Planet planetToUpdate) {
        // Fetch the Planet from db
        Planet planetFromDb = planetRepository.findById(id).get();
        planetFromDb.setName(planetToUpdate.getName());
        planetFromDb.setNumberOfMoons(planetToUpdate.getNumberOfMoons());
        return planetRepository.save(planetFromDb);
    }

    /**
     * Delete Planet by Id.
     * @param id Id
     */
    @Override
    public void deletePlanetById(final Long id) {
        planetRepository.deleteById(id);
    }

    /**
     * Search Planet by name containing.
     * @param searchString SearchString
     * @return Search result
     */
    @Override
    public List<Planet> getPlanetByNameContaining(final String searchString) {
        return planetRepository.findByNameContaining(searchString);
    }

    /**
     * Search Planet by name like.
     * @param searchString SearchString
     * @return Search result
     */
    @Override
    public List<Planet> getPlanetByNameLike(final String searchString) {
        return planetRepository.findByNameLike(searchString);
    }
}

We auto-wire the repository into service layer, and then auto-wire the service layer into controller.

Controller

PlanetController.java

package com.logicalsapien.sprintbooth2.controller;

import com.logicalsapien.sprintbooth2.entity.Planet;
import com.logicalsapien.sprintbooth2.service.PlanetService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

/**
 * Planet Rest Controller.
 */
@RestController
@RequestMapping("/planet")
public class PlanetController {

    /**
     * Autowiring the Planet Service.
     */
    @Autowired
    private PlanetService planetService;

    /**
     * get Api to return list of all planets.
     * @return List of Planets
     */
    @GetMapping()
    public ResponseEntity<List<Planet>> getAllPlanets() {
        List<Planet> planetList = planetService.getAllPlanets();
        return new ResponseEntity<>(planetList, HttpStatus.OK);
    }

    /**
     * get Api to return the Planet by Id.
     * @param id Id
     * @return Planets
     */
    @GetMapping("/{id}")
    public ResponseEntity<Planet> getPlanetById(
            @PathVariable("id") final Long id) {
        Planet planet = planetService.getPlanetById(id);
        return new ResponseEntity<>(planet, HttpStatus.OK);
    }

    /**
     * Api to save new Planet.
     * @param planet Planet to add
     * @return Saved Planet
     */
    @PostMapping()
    public ResponseEntity<Planet> savePlanet(
            @RequestBody final  Planet planet) {
        Planet savedPlanet = planetService.savePlanet(planet);
        return new ResponseEntity<>(savedPlanet, HttpStatus.CREATED);
    }

    /**
     * Api to update an existing Planet.
     * @param id Id to update
     * @param planetToUpdate Planet to update
     * @return Updated Planet
     */
    @PutMapping("/{id}")
    public ResponseEntity<Planet> updatePlanetById(
            @PathVariable("id") final Long id,
            @RequestBody final Planet planetToUpdate) {
        Planet updatedPlanet
                = planetService.updatePlanetById(id, planetToUpdate);
        return new ResponseEntity<>(updatedPlanet, HttpStatus.OK);
    }

    /**
     * Api to delete a planet.
     * @param id Id to delete
     * @return Success Message
     */
    @DeleteMapping("/{id}")
    public ResponseEntity<String> deletePlanetById(
            @PathVariable("id") final Long id) {
        planetService.deletePlanetById(id);
        return new ResponseEntity<>("Success", HttpStatus.OK);
    }

    /**
     * get Api to return search planet by name containing.
     * @param searchString Search String
     * @return Search Result
     */
    @GetMapping("/search1/{searchString}")
    public ResponseEntity<List<Planet>> getPlanetByNameContaining(
            @PathVariable("searchString") final String searchString) {
        List<Planet> planetList
                = planetService.getPlanetByNameContaining(searchString);
        return new ResponseEntity<>(planetList, HttpStatus.OK);
    }

    /**
     * get Api to return search planet by name like.
     * @param searchString Search String
     * @return Search Result
     */
    @GetMapping("/search2/{searchString}")
    public ResponseEntity<List<Planet>> getPlanetByNameLike(
            @PathVariable("searchString") final String searchString) {
        List<Planet> planetList
                = planetService.getPlanetByNameLike(searchString);
        return new ResponseEntity<>(planetList, HttpStatus.OK);
    }
}

Testing

We have all set. Start the application. We can test all the APIs using a REST client such as PostMan.

Get All Planets

Add a new Planet

Update Planet info

Delete a Planet

Search 1

Search 2

Conclusion

I hope that gave you a fairly good idea on how to get started with a Spring Boot CRUD API with H2

Additional Readings

Notes

View the full code in Github

CI Build on Travis

Code quality on Sonar Cloud