Main Tutorials

Spring Boot + Spring Data JPA example

spring boot spring data jpa

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:

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.

project directory

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.

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
         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.

Book.java

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.

BookRepository.java

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.

BookService.java

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.

MainApplication.java

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.

8. Download Source Code

$ git clone https://github.com/mkyong/spring-boot.git

$ cd spring-data-jpa

$ ./mvnw spring-boot:run

9. References

About Author

author image
Founder of Mkyong.com, love Java and open source stuff. Follow him on Twitter. If you like my tutorials, consider make a donation to these charities.

Comments

Subscribe
Notify of
3 Comments
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Akash Mulik
4 years ago

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??

Amlan Mohanty
4 years ago
Reply to  Akash Mulik

Yes.. Both the declaration and implementation is there in parent class. That’s the beauty of CrudRepository.

Stef
5 months ago

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