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 nameplanet
will be created in db. - And and id field with
@Id
is mandatory @GeneratedValue
is to specify the column increment strategy. Default isAuto
strategy. Here we specifiedstrategy= 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. TheCrudRepository
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
- Read on Spring Boot CRUD API with MongoDB example.
- How to build & run the application via Gradle commands
Notes
View the full code in Github
CI Build on Travis
Code quality on Sonar Cloud