Let me explain it briefly.
- Tutorial data model class corresponds to entity and table tutorials.
- TutorialRepository is an interface that extends JpaRepository for CRUD methods and custom finder methods. It will be autowired in TutorialController.
- TutorialController is a RestController which has request mapping methods for RESTful requests such as: getAllTutorials, createTutorial, updateTutorial, deleteTutorial, findByPublished…
- Configuration for Spring Datasource, JPA & Hibernate in application.properties.
- pom.xml contains dependencies for Spring Boot and H2 Database.
- pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>springbootjpah2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-jpa-h2</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>2.7.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Display the project dependencies.
> mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] --------------------< com.example:springbootjpah2 >---------------------
[INFO] Building spring-boot-jpa-h2 0.0.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:3.3.0:tree (default-cli) @ springbootjpah2 ---
[INFO] com.example:springbootjpah2:jar:0.0.1-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.7.1:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.7.1:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.7.1:compile
[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.11:compile
[INFO] | | | | \- ch.qos.logback:logback-core:jar:1.2.11:compile
[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.17.2:compile
[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.17.2:compile
[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.36:compile
[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] | | \- org.yaml:snakeyaml:jar:1.30:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.7.1:compile
[INFO] | | +- com.fasterxml.jackson.core:jackson-databind:jar:2.13.3:compile
[INFO] | | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.3:compile
[INFO] | | | \- com.fasterxml.jackson.core:jackson-core:jar:2.13.3:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.13.3:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.13.3:compile
[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.13.3:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.7.1:compile
[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.64:compile
[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.64:compile
[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.64:compile
[INFO] | +- org.springframework:spring-web:jar:5.3.21:compile
[INFO] | | \- org.springframework:spring-beans:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-webmvc:jar:5.3.21:compile
[INFO] | +- org.springframework:spring-aop:jar:5.3.21:compile
[INFO] | +- org.springframework:spring-context:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-expression:jar:5.3.21:compile
[INFO] +- org.springframework.boot:spring-boot-devtools:jar:2.7.1:runtime
[INFO] | +- org.springframework.boot:spring-boot:jar:2.7.1:compile
[INFO] | \- org.springframework.boot:spring-boot-autoconfigure:jar:2.7.1:compile
[INFO] +- com.h2database:h2:jar:2.1.214:runtime
[INFO] +- org.springframework.boot:spring-boot-configuration-processor:jar:2.7.1:compile
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.7.1:test
[INFO] | +- org.springframework.boot:spring-boot-test:jar:2.7.1:test
[INFO] | +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.7.1:test
[INFO] | +- com.jayway.jsonpath:json-path:jar:2.7.0:test
[INFO] | | +- net.minidev:json-smart:jar:2.4.8:test
[INFO] | | | \- net.minidev:accessors-smart:jar:2.4.8:test
[INFO] | | | \- org.ow2.asm:asm:jar:9.1:test
[INFO] | | \- org.slf4j:slf4j-api:jar:1.7.36:compile
[INFO] | +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:compile
[INFO] | | \- jakarta.activation:jakarta.activation-api:jar:1.2.2:compile
[INFO] | +- org.assertj:assertj-core:jar:3.22.0:test
[INFO] | +- org.hamcrest:hamcrest:jar:2.2:test
[INFO] | +- org.junit.jupiter:junit-jupiter:jar:5.8.2:test
[INFO] | | +- org.junit.jupiter:junit-jupiter-api:jar:5.8.2:test
[INFO] | | | +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO] | | | +- org.junit.platform:junit-platform-commons:jar:1.8.2:test
[INFO] | | | \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO] | | +- org.junit.jupiter:junit-jupiter-params:jar:5.8.2:test
[INFO] | | \- org.junit.jupiter:junit-jupiter-engine:jar:5.8.2:test
[INFO] | | \- org.junit.platform:junit-platform-engine:jar:1.8.2:test
[INFO] | +- org.mockito:mockito-core:jar:4.5.1:test
[INFO] | | +- net.bytebuddy:byte-buddy:jar:1.12.11:compile
[INFO] | | +- net.bytebuddy:byte-buddy-agent:jar:1.12.11:test
[INFO] | | \- org.objenesis:objenesis:jar:3.2:test
[INFO] | +- org.mockito:mockito-junit-jupiter:jar:4.5.1:test
[INFO] | +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] | | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] | +- org.springframework:spring-core:jar:5.3.21:compile
[INFO] | | \- org.springframework:spring-jcl:jar:5.3.21:compile
[INFO] | +- org.springframework:spring-test:jar:5.3.21:test
[INFO] | \- org.xmlunit:xmlunit-core:jar:2.9.0:test
[INFO] +- javax.persistence:javax.persistence-api:jar:2.2:compile
[INFO] \- org.springframework.boot:spring-boot-starter-data-jpa:jar:2.7.1:compile
[INFO] +- org.springframework.boot:spring-boot-starter-aop:jar:2.7.1:compile
[INFO] | \- org.aspectj:aspectjweaver:jar:1.9.7:compile
[INFO] +- org.springframework.boot:spring-boot-starter-jdbc:jar:2.7.1:compile
[INFO] | +- com.zaxxer:HikariCP:jar:4.0.3:compile
[INFO] | \- org.springframework:spring-jdbc:jar:5.3.21:compile
[INFO] +- jakarta.transaction:jakarta.transaction-api:jar:1.3.3:compile
[INFO] +- jakarta.persistence:jakarta.persistence-api:jar:2.2.3:compile
[INFO] +- org.hibernate:hibernate-core:jar:5.6.9.Final:compile
[INFO] | +- org.jboss.logging:jboss-logging:jar:3.4.3.Final:compile
[INFO] | +- antlr:antlr:jar:2.7.7:compile
[INFO] | +- org.jboss:jandex:jar:2.4.2.Final:compile
[INFO] | +- com.fasterxml:classmate:jar:1.5.1:compile
[INFO] | +- org.hibernate.common:hibernate-commons-annotations:jar:5.1.2.Final:compile
[INFO] | \- org.glassfish.jaxb:jaxb-runtime:jar:2.3.6:compile
[INFO] | +- org.glassfish.jaxb:txw2:jar:2.3.6:compile
[INFO] | +- com.sun.istack:istack-commons-runtime:jar:3.0.12:compile
[INFO] | \- com.sun.activation:jakarta.activation:jar:1.2.2:runtime
[INFO] +- org.springframework.data:spring-data-jpa:jar:2.7.1:compile
[INFO] | +- org.springframework.data:spring-data-commons:jar:2.7.1:compile
[INFO] | +- org.springframework:spring-orm:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-tx:jar:5.3.21:compile
[INFO] \- org.springframework:spring-aspects:jar:5.3.21:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.500 s
[INFO] Finished at: 2022-07-21T09:35:05+07:00
[INFO] ------------------------------------------------------------------------
These are APIs that we need to provide:
Methods | Urls | Actions |
---|---|---|
POST | /api/tutorials | create new Tutorial |
GET | /api/tutorials | retrieve all Tutorials |
GET | /api/tutorials/:id | retrieve a Tutorial by :id |
PUT | /api/tutorials/:id | update a Tutorial by :id |
DELETE | /api/tutorials/:id | delete a Tutorial by :id |
DELETE | /api/tutorials | delete all Tutorials |
GET | /api/tutorials/published | find all published Tutorials |
GET | /api/tutorials?title=[keyword] | find all Tutorials which title contains keyword |
- We make CRUD operations & finder methods with Spring Data JPA’s JpaRepository.
- The database will be H2 Database (in memory or on disk) by configuring project dependency & datasource.
- Configure Spring Boot, JPA, h2, Hibernate: Under src/main/resources folder, open application.properties and write these lines.
-
spring.datasource.url
:jdbc:h2:mem:[database-name]
for In-memory database andjdbc:h2:file:[path/database-name]
for disk-based database. -
spring.datasource.username & spring.datasource.password
properties are the same as your database installation. -
Spring Boot uses Hibernate for JPA implementation, we configure
H2Dialect
for H2 Database -
spring.jpa.hibernate.ddl-auto
is used for database initialization. We set the value toupdate
value so that a table will be created in the database automatically corresponding to defined data model. Any change to the model will also trigger an update to the table. For production, this property should bevalidate
. -
spring.h2.console.enabled=true
tells the Spring to start H2 Database administration tool and you can access this tool on the browser:http://localhost:8080/h2-console
. -
spring.h2.console.path=/h2-ui
is for H2 console’s url, so the default urlhttp://localhost:8080/h2-console
will change tohttp://localhost:8080/h2-ui
.
-
Data model is Tutorial with four fields: id, title, description, published.
package com.example.springbootjpah2.model;
import javax.persistence.*;
@Entity
@Table(name = "tutorials")
public class Tutorial {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
@Column(name = "title")
private String title;
@Column(name = "description")
private String description;
@Column(name = "published")
private boolean published;
public Tutorial() {
}
public Tutorial(String title, String description, boolean published) {
this.title = title;
this.description = description;
this.published = published;
}
public long getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public boolean isPublished() {
return published;
}
public void setPublished(boolean isPublished) {
this.published = isPublished;
}
@Override
public String toString() {
return "Tutorial [id=" + id + ", title=" + title + ", desc=" + description + ", published=" + published + "]";
}
}
- @Entity annotation indicates that the class is a persistent Java class.
- @Table annotation provides the table that maps this entity.
- @Id annotation is for the primary key.
- @GeneratedValue annotation is used to define generation strategy for the primary key. GenerationType.AUTO means Auto Increment field.
- @Column annotation is used to define the column in database that maps annotated field.
Let’s create a repository to interact with Tutorials from the database.
- TutorialRepository.java
package com.example.springbootjpah2.repository;
import com.example.springbootjpah2.model.Tutorial;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface TutorialRepository extends JpaRepository<Tutorial, Long> {
List<Tutorial> findByPublished(boolean published);
List<Tutorial> findByTitleContaining(String title);
}
Now we can use JpaRepository’s methods: save(), findOne(), findById(), findAll(), count(), delete(), deleteById(),…
without implementing these methods.
We also define custom finder methods:
– findByPublished()
: returns all Tutorials with published having value as input published.
– findByTitleContaining()
: returns all Tutorials which title contains input title.
Finally, we create a controller that provides APIs for creating, retrieving, updating, deleting and finding Tutorials.
package com.example.springbootjpah2.controller;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.example.springbootjpah2.model.Tutorial;
import com.example.springbootjpah2.repository.TutorialRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin(origins = "http://localhost:8081")
@RestController
@RequestMapping("/api")
public class TutorialController {
@Autowired
TutorialRepository tutorialRepository;
@GetMapping("/tutorials")
public ResponseEntity<List<Tutorial>> getAllTutorials(@RequestParam(required = false) String title) {
try {
List<Tutorial> tutorials = new ArrayList<Tutorial>();
if (title == null)
tutorialRepository.findAll().forEach(tutorials::add);
else
tutorialRepository.findByTitleContaining(title).forEach(tutorials::add);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> getTutorialById(@PathVariable("id") long id) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
return new ResponseEntity<>(tutorialData.get(), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@PostMapping("/tutorials")
public ResponseEntity<Tutorial> createTutorial(@RequestBody Tutorial tutorial) {
try {
Tutorial _tutorial = tutorialRepository
.save(new Tutorial(tutorial.getTitle(), tutorial.getDescription(), false));
return new ResponseEntity<>(_tutorial, HttpStatus.CREATED);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@PutMapping("/tutorials/{id}")
public ResponseEntity<Tutorial> updateTutorial(@PathVariable("id") long id, @RequestBody Tutorial tutorial) {
Optional<Tutorial> tutorialData = tutorialRepository.findById(id);
if (tutorialData.isPresent()) {
Tutorial _tutorial = tutorialData.get();
_tutorial.setTitle(tutorial.getTitle());
_tutorial.setDescription(tutorial.getDescription());
_tutorial.setPublished(tutorial.isPublished());
return new ResponseEntity<>(tutorialRepository.save(_tutorial), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
@DeleteMapping("/tutorials/{id}")
public ResponseEntity<HttpStatus> deleteTutorial(@PathVariable("id") long id) {
try {
tutorialRepository.deleteById(id);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@DeleteMapping("/tutorials")
public ResponseEntity<HttpStatus> deleteAllTutorials() {
try {
tutorialRepository.deleteAll();
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@GetMapping("/tutorials/published")
public ResponseEntity<List<Tutorial>> findByPublished() {
try {
List<Tutorial> tutorials = tutorialRepository.findByPublished(true);
if (tutorials.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(tutorials, HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
}
- @CrossOrigin is for configuring allowed origins.
- @RestController annotation is used to define a controller and to indicate that the return value of the methods should be be bound to the web response body.
- @RequestMapping("/api") declares that all Apis’ url in the controller will start with /api.
- We use @Autowired to inject TutorialRepository bean to local variable.
Click to main function to run application
Let’s open H2 console with url: http://localhost:8080/h2-ui
- For In-memory database:
You can get JBDC path in file
application.properties
- For on Disk database:
Click on Connect button, then check H2 database, you can see things like this:
Create some Tutorials:
H2 database
tutorials
table after that:
Update some Tutorials:
Retrieve all Tutorials:
Retrieve a Tutorial by Id:
Find all published Tutorials:
Find all Tutorials which title contains string 'Hunger':
Delete a Tutorial:
Delete all Tutorials:
https://github.com/java-cake/spring-boot/tree/main/spring-boot-jpa-h2
Top comments (0)