Spring Boot + Spring Data JPA example
This article shows how to use Spring Data JPA to perform CRUD operation into a H2 in-memory database.
Technologies used:
- Spring Boot 3.1.2
- Spring Data JPA (Hibernate 6 is the default JPA implementation)
- H2 in-memory database
- Maven
- Java 17
Table of contents:
- 1. Project Directory
- 2. Project Dependencies
- 3. Spring Data JPA – Entity
- 4. Spring Data JPA – Repository
- 5. Service Component
- 6. Spring Boot Application
- 7. Demo
- 8. Download Source Code
- 9. References
Note
By default, Spring Data JPA uses HikariCP as the database connection pool. Read this Spring Boot algorithm to choose a pool implementation.
1. Project Directory
Below is a standard Maven project structure for this project.
2. Project Dependencies
We only need to declare spring-boot-starter-data-jpa
, and it will get Spring Data, Hibernate, HikariCP, and all database related dependencies automatically.
<?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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-jpa</artifactId>
<packaging>jar</packaging>
<name>Spring Boot Spring Data JPA</name>
<url>https://mkyong.com</url>
<version>1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
</parent>
<properties>
<java.version>17</java.version>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</properties>
<dependencies>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- in-memory database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
Display the project dependencies in tree format.
mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] --------------< org.springframework.boot:spring-data-jpa >--------------
[INFO] Building Spring Boot Spring Data JPA 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:3.5.0:tree (default-cli) @ spring-data-jpa ---
[INFO] org.springframework.boot:spring-data-jpa:jar:1.0
[INFO] +- org.springframework.boot:spring-boot-starter-data-jpa:jar:3.1.2:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-aop:jar:3.1.2:compile
[INFO] | | +- org.springframework:spring-aop:jar:6.0.11:compile
[INFO] | | \- org.aspectj:aspectjweaver:jar:1.9.19:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-jdbc:jar:3.1.2:compile
[INFO] | | +- com.zaxxer:HikariCP:jar:5.0.1:compile
[INFO] | | \- org.springframework:spring-jdbc:jar:6.0.11:compile
[INFO] | +- org.hibernate.orm:hibernate-core:jar:6.2.6.Final:compile
[INFO] | | +- jakarta.persistence:jakarta.persistence-api:jar:3.1.0:compile
[INFO] | | +- jakarta.transaction:jakarta.transaction-api:jar:2.0.1:compile
[INFO] | | +- org.jboss.logging:jboss-logging:jar:3.5.3.Final:runtime
[INFO] | | +- org.hibernate.common:hibernate-commons-annotations:jar:6.0.6.Final:runtime
[INFO] | | +- io.smallrye:jandex:jar:3.0.5:runtime
[INFO] | | +- com.fasterxml:classmate:jar:1.5.1:runtime
[INFO] | | +- net.bytebuddy:byte-buddy:jar:1.14.5:runtime
[INFO] | | +- org.glassfish.jaxb:jaxb-runtime:jar:4.0.3:runtime
[INFO] | | | \- org.glassfish.jaxb:jaxb-core:jar:4.0.3:runtime
[INFO] | | | +- org.eclipse.angus:angus-activation:jar:2.0.1:runtime
[INFO] | | | +- org.glassfish.jaxb:txw2:jar:4.0.3:runtime
[INFO] | | | \- com.sun.istack:istack-commons-runtime:jar:4.1.2:runtime
[INFO] | | +- jakarta.inject:jakarta.inject-api:jar:2.0.1:runtime
[INFO] | | \- org.antlr:antlr4-runtime:jar:4.10.1:compile
[INFO] | +- org.springframework.data:spring-data-jpa:jar:3.1.2:compile
[INFO] | | +- org.springframework.data:spring-data-commons:jar:3.1.2:compile
[INFO] | | +- org.springframework:spring-orm:jar:6.0.11:compile
[INFO] | | +- org.springframework:spring-context:jar:6.0.11:compile
[INFO] | | | \- org.springframework:spring-expression:jar:6.0.11:compile
[INFO] | | +- org.springframework:spring-tx:jar:6.0.11:compile
[INFO] | | +- org.springframework:spring-beans:jar:6.0.11:compile
[INFO] | | +- jakarta.annotation:jakarta.annotation-api:jar:2.1.1:compile
[INFO] | | \- org.slf4j:slf4j-api:jar:2.0.7:compile
[INFO] | \- org.springframework:spring-aspects:jar:6.0.11:compile
[INFO] +- com.h2database:h2:jar:2.1.214:compile
[INFO] \- org.springframework.boot:spring-boot-starter-test:jar:3.1.2:test
[INFO] +- org.springframework.boot:spring-boot-starter:jar:3.1.2:compile
[INFO] | +- org.springframework.boot:spring-boot:jar:3.1.2:compile
[INFO] | +- org.springframework.boot:spring-boot-autoconfigure:jar:3.1.2:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-logging:jar:3.1.2:compile
[INFO] | | +- ch.qos.logback:logback-classic:jar:1.4.8:compile
[INFO] | | | \- ch.qos.logback:logback-core:jar:1.4.8:compile
[INFO] | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.20.0:compile
[INFO] | | | \- org.apache.logging.log4j:log4j-api:jar:2.20.0:compile
[INFO] | | \- org.slf4j:jul-to-slf4j:jar:2.0.7:compile
[INFO] | \- org.yaml:snakeyaml:jar:1.33:compile
[INFO] +- org.springframework.boot:spring-boot-test:jar:3.1.2:test
[INFO] +- org.springframework.boot:spring-boot-test-autoconfigure:jar:3.1.2:test
[INFO] +- com.jayway.jsonpath:json-path:jar:2.8.0:test
[INFO] +- jakarta.xml.bind:jakarta.xml.bind-api:jar:4.0.0:runtime
[INFO] | \- jakarta.activation:jakarta.activation-api:jar:2.1.2:runtime
[INFO] +- net.minidev:json-smart:jar:2.4.11:test
[INFO] | \- net.minidev:accessors-smart:jar:2.4.11:test
[INFO] | \- org.ow2.asm:asm:jar:9.3:test
[INFO] +- org.assertj:assertj-core:jar:3.24.2:test
[INFO] +- org.hamcrest:hamcrest:jar:2.2:test
[INFO] +- org.junit.jupiter:junit-jupiter:jar:5.9.3:test
[INFO] | +- org.junit.jupiter:junit-jupiter-api:jar:5.9.3:test
[INFO] | | +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO] | | +- org.junit.platform:junit-platform-commons:jar:1.9.3:test
[INFO] | | \- org.apiguardian:apiguardian-api:jar:1.1.2:test
[INFO] | +- org.junit.jupiter:junit-jupiter-params:jar:5.9.3:test
[INFO] | \- org.junit.jupiter:junit-jupiter-engine:jar:5.9.3:test
[INFO] | \- org.junit.platform:junit-platform-engine:jar:1.9.3:test
[INFO] +- org.mockito:mockito-core:jar:5.3.1:test
[INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.14.5:test
[INFO] | \- org.objenesis:objenesis:jar:3.3:test
[INFO] +- org.mockito:mockito-junit-jupiter:jar:5.3.1:test
[INFO] +- org.skyscreamer:jsonassert:jar:1.5.1:test
[INFO] | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] +- org.springframework:spring-core:jar:6.0.11:compile
[INFO] | \- org.springframework:spring-jcl:jar:6.0.11:compile
[INFO] +- org.springframework:spring-test:jar:6.0.11:test
[INFO] \- org.xmlunit:xmlunit-core:jar:2.9.1:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.094 s
[INFO] Finished at: 2023-09-08T16:43:25+08:00
[INFO] ------------------------------------------------------------------------
3. Spring Data JPA – Entity
A Book
object as JPA entity.
package com.mkyong;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import java.math.BigDecimal;
import java.time.LocalDate;
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String title;
private BigDecimal price;
private LocalDate publishDate;
// for JPA only, no use
public Book() {
}
public Book(String title, BigDecimal price, LocalDate publishDate) {
this.title = title;
this.price = price;
this.publishDate = publishDate;
}
//getters , setters ...
}
4. Spring Data JPA – Repository
Creates BookRepository
interface and extends the Spring Data JpaRepository
; it will automatically create the CRUD (create, read, update, and delete) methods and implementation at runtime.
Spring Data JPA also lets us define custom queries using @Query
annotation, and those findBy{field-name}
works perfectly as long as the `{field-name} exists in the entity object.
package com.mkyong;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.time.LocalDate;
import java.util.List;
// Spring Data JPA creates CRUD implementation at runtime automatically.
public interface BookRepository extends JpaRepository<Book, Long> {
// it works if it matches the book field name
List<Book> findByTitle(String title);
// Custom Query
@Query("SELECT b FROM Book b WHERE b.publishDate > :date")
List<Book> findByPublishedDateAfter(@Param("date") LocalDate date);
}
5. Service Component
Create a BookService
component and @Autowired
the BookRepository
.
package com.mkyong;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
@Service
public class BookService {
@Autowired
private BookRepository bookRepository;
public List<Book> findAll() {
return bookRepository.findAll();
}
public Optional<Book> findById(Long id) {
return bookRepository.findById(id);
}
public Book save(Book book) {
return bookRepository.save(book);
}
public void deleteById(Long id) {
bookRepository.deleteById(id);
}
public List<Book> findByPublishedDateAfter(LocalDate date) {
return bookRepository.findByPublishedDateAfter(date);
}
}
6. Spring Boot Application
Create a Spring Boot application and a CommandLineRunner
bean to run tests for the Spring Data JPA application.
package com.mkyong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Optional;
@SpringBootApplication
public class MainApplication {
private static final Logger log = LoggerFactory.getLogger(MainApplication.class);
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
// Spring runs CommandLineRunner bean when Spring Boot App starts
@Bean
public CommandLineRunner demo(BookRepository bookRepository) {
return (args) -> {
Book b1 = new Book("Book A", BigDecimal.valueOf(9.99), LocalDate.of(2023, 8, 31));
Book b2 = new Book("Book B", BigDecimal.valueOf(19.99), LocalDate.of(2023, 7, 31));
Book b3 = new Book("Book C", BigDecimal.valueOf(29.99), LocalDate.of(2023, 6, 10));
Book b4 = new Book("Book D", BigDecimal.valueOf(39.99), LocalDate.of(2023, 5, 5));
// save a few books, ID auto increase, expect 1, 2, 3, 4
bookRepository.save(b1);
bookRepository.save(b2);
bookRepository.save(b3);
bookRepository.save(b4);
// find all books
log.info("findAll(), expect 4 books");
log.info("-------------------------------");
for (Book book : bookRepository.findAll()) {
log.info(book.toString());
}
log.info("\n");
// find book by ID
Optional<Book> optionalBook = bookRepository.findById(1L);
optionalBook.ifPresent(obj -> {
log.info("Book found with findById(1L):");
log.info("--------------------------------");
log.info(obj.toString());
log.info("\n");
});
// find book by title
log.info("Book found with findByTitle('Book B')");
log.info("--------------------------------------------");
bookRepository.findByTitle("Book C").forEach(b -> {
log.info(b.toString());
log.info("\n");
});
// find book by published date after
log.info("Book found with findByPublishedDateAfter(), after 2023/7/1");
log.info("--------------------------------------------");
bookRepository.findByPublishedDateAfter(LocalDate.of(2023, 7, 1)).forEach(b -> {
log.info(b.toString());
log.info("\n");
});
// delete a book
bookRepository.deleteById(2L);
log.info("Book delete where ID = 2L");
log.info("--------------------------------------------");
// find all books
log.info("findAll() again, expect 3 books");
log.info("-------------------------------");
for (Book book : bookRepository.findAll()) {
log.info(book.toString());
}
log.info("\n");
};
}
}
7. Demo
Run the Spring Boot application using ./mvnw spring-boot:run
.
$ ./mvnw spring-boot:run
INFO com.mkyong.MainApplication - Started MainApplication in 2.546 seconds (process running for 2.826)
INFO com.mkyong.MainApplication - findAll(), expect 4 books
INFO com.mkyong.MainApplication - -------------------------------
INFO com.mkyong.MainApplication - Book{id=1, title='Book A', price=9.99, publishDate=2023-08-31}
INFO com.mkyong.MainApplication - Book{id=2, title='Book B', price=19.99, publishDate=2023-07-31}
INFO com.mkyong.MainApplication - Book{id=3, title='Book C', price=29.99, publishDate=2023-06-10}
INFO com.mkyong.MainApplication - Book{id=4, title='Book D', price=39.99, publishDate=2023-05-05}
INFO com.mkyong.MainApplication -
INFO com.mkyong.MainApplication - Book found with findById(1L):
INFO com.mkyong.MainApplication - --------------------------------
INFO com.mkyong.MainApplication - Book{id=1, title='Book A', price=9.99, publishDate=2023-08-31}
INFO com.mkyong.MainApplication -
INFO com.mkyong.MainApplication - Book found with findByTitle('Book B')
INFO com.mkyong.MainApplication - --------------------------------------------
INFO com.mkyong.MainApplication - Book{id=3, title='Book C', price=29.99, publishDate=2023-06-10}
INFO com.mkyong.MainApplication -
INFO com.mkyong.MainApplication - Book found with findByPublishedDateAfter(), after 2023/7/1
INFO com.mkyong.MainApplication - --------------------------------------------
INFO com.mkyong.MainApplication - Book{id=1, title='Book A', price=9.99, publishDate=2023-08-31}
INFO com.mkyong.MainApplication -
INFO com.mkyong.MainApplication - Book{id=2, title='Book B', price=19.99, publishDate=2023-07-31}
INFO com.mkyong.MainApplication -
INFO com.mkyong.MainApplication - Book delete where ID = 2L
INFO com.mkyong.MainApplication - --------------------------------------------
INFO com.mkyong.MainApplication - findAll() again, expect 3 books
INFO com.mkyong.MainApplication - -------------------------------
INFO com.mkyong.MainApplication - Book{id=1, title='Book A', price=9.99, publishDate=2023-08-31}
INFO com.mkyong.MainApplication - Book{id=3, title='Book C', price=29.99, publishDate=2023-06-10}
INFO com.mkyong.MainApplication - Book{id=4, title='Book D', price=39.99, publishDate=2023-05-05}
INFO com.mkyong.MainApplication -
Further Reading
The below article shows how to use the @DataJpaTest
annotation to test the Spring Data JPA application.
Please add description for following line:
public interface BookRepository extends CrudRepository {
We have written only one method here. So findAll() and findById() methods are in parent interface??
Yes.. Both the declaration and implementation is there in parent class. That’s the beauty of CrudRepository.
Hello mkyong,
thank you for your example above. However, you have been declaring the class BookService without using it. Instead you are implementing a CommandLineRunner in your MainApplication-class.
I want to get a new project started and have choosen a similar architecture as your BookService-class. However, when I do not implement a CommandLineRunner in the MainApplication (since the program is supposed to turn out bigger than this), I am not able to call a method in the service class without getting the massage that the autowired repository is null. So, obviously the autowiring does not work. Do you have an idea about the reason.
Thus, it would be great if you could show your example without addressing the BookRepository only from within the MainApplication, but by means of the BookService-Class you have created but not used.
Thank you very much in advance.
Stef