Spring Data JPA Tutorial

Last modified: July 4, 2020

Ask all your coding questions @ codequery.io

Spring Data JPA

Spring Data JPA Tutorial

Spring Data JPA Example

Spring Data JPA CRUD Example

Spring Data JPA vs Hibernate

Spring Data JPA Interview Questions

What is Spring Data JPA?

Spring Data JPA makes it easier to work with JPA providers. A JPA provider is an object relational mapping (ORM) tool that implements the JPA specification. The JPA specification defines how Java objects represent relational database tables.

Spring Data JPA is an abstraction for working with JPA providers such as Hibernate. Using Spring Data JPA, you can avoid the boilerplate code associated with managing transactions and entity managers for providers like Hibernate.

What's in this tutorial?

In this Spring Data JPA tutorial, see how to create a Spring Boot app that manages a one-to-many relationship between authors and books using Spring Data JPA and MySQL.

1. Creating the project

You can easily create a project with all the necessary dependencies using maven.

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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/>
    </parent>
    <groupId>mygroup</groupId>
    <artifactId>bookservice</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
</project>

spring-boot-starter-data-jpa includes dependencies for Hibernate and Spring Data JPA abstractions.

mysql-connector-java includes the MySQL database driver for connecting to a datasource.

2.Configuring the database

Spring provides abstractions that hide the implementation details of connecting to a MySQL instance. Specifying the following in application.properties configures Spring Data JPA to work with the database:

src/main/resources/applicatiton.properties

spring.datasource.url=jdbc:mysql://localhost:3306/books_service
spring.datasource.username=<DATABASE USERNAME>
spring.datasource.password=<DATABASE PASSSWORD>
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.database-platform = org.hibernate.dialect.MySQL55Dialect
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto = create
spring.jpa.show-sql=true

3. Create the entities

Entities are classes that map to tables in the database. For each table in the database, you can create a corresponding entity class:

Author Entity

src/main/java/bookservice/model/Author.java
package bookservice.model;


import javax.persistence.*;
import java.util.Set;


@Entity
public class Author {
    @Id
    @GeneratedValue
    private Long id;

    private String firstName;

    @Column(unique=true)
    private String lastName;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
    private Set books;

    protected Author() {}

    public Author(String first, String last) {
        this.firstName = first;
        this.lastName  = last;
    }

    public Set getBooks(){
        return this.books;
    }

    public String getFullName(){
        return this.firstName + " " + this.lastName;
    }
}

Book Entity

src/main/java/bookservice/model/Book.java

package bookservice.model;

import javax.persistence.*;

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.EAGER)
    private Author author;

    @Column(unique=true)
    private String title;

    protected Book() {}

    public Book(String title){
        this.title = title;
    }

    public String getTitle(){
        return this.title;
    }

    public void setAuthor(Author author){
        this.author = author;
    }

    public Author getAuthor(){
        return this.author;
    }

}

The @Entity annotation specifies a class corresponds to a table in the database. Each member of the class corresponds to a column in the database. For example, the author table will have a column first_name and last_name.

You can override default column names via @Column. This annotation allows you to specify optional name and constraints/validations like uniqueness and required.

The @Id annotation signifies a class member is the primary key for that table. This is often used in combination with @GeneratedValue to automatically manage id generation for you.

Managing relationships between entities

Notice the use of @OneToMany and @ManyToOne. These annotations specify a one-to-many association between authors and books. One author can have many books.

This type of relationship requires the book table have a foreign key referencing the author table. In this sense, the book table "owns the relationship".

To achieve this relationship, @OneToMany is used to associate a set of books with an author. The mappedBy = "authors" attribute points to the associated field on the owning entity in the relationship. For these reasons, the @ManyToOne annotation is used on the author field for Book.

FetchType.EAGER vs FetchType.LAZY

Notice how a FetchType is specified for each one-to-many annotation. This indicates whether associated data will be eagerly or lazily loaded. Lazy loading can save memory and processing as associated records are only retrieved when asked for. Eager loading can be better if associated data will always be accessed by the application.

4. Create the repositories

Repositories are how the application interacts with the database. Extending Spring Data JPA interfaces like CrudRepository allows for an easy data access layer implementation.

Author Repository

src/main/java/bookservice/repository/AuthorRepository.java

package bookservice.repository;

import bookservice.model.Author;
import org.springframework.data.repository.CrudRepository;

public interface AuthorRepository extends CrudRepository {
}

Book Repository

src/main/java/bookservice/repository/BookRepository.java
package bookservice.repository;

import bookservice.model.Book;
import org.springframework.data.repository.CrudRepository;

public interface BookRepository extends CrudRepository {
    Book findByTitle(String title);
}

Notice how by simply extending the CrudRepository interface is enough to implement a basic data access layer between your application and the database. Supplying the entity and id type <Author, Long> is enough for Spring Data to automatically implement the beans necessary to perform CRUD operations on the database.

But how is this possible?

When @EnableJpaRepositories is specified (by default with later versions of Spring), Spring automatically implements JPA repositories based on these interfaces. This includes repository methods like:

save()

findAll()

findById()

delete()

You can also define your own method signatures:

Book findByTitle(String title);

Based on the method signature Spring can implement the necessary methods for running this query.

5. Running the application

Application.java

src/main/java/bookservice/Application.java

package bookservice;

import bookservice.model.Author;
import bookservice.model.Book;
import bookservice.repository.AuthorRepository;
import bookservice.repository.BookRepository;
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;

@SpringBootApplication
public class Application {

    private static final Logger log =
            LoggerFactory.getLogger(Application.class);

    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner demo(BookRepository bookRepo, AuthorRepository authRepo) {
        return (args) -> {
            //create a new author
            Author author = new Author("JK", "Rowling");
            //create a new book
            Book book = new Book("Harry Potter");
            //save author to db
            authRepo.save(author);
            //associate author with book
            book.setAuthor(author);
            //save book
            bookRepo.save(book);
            //read book from db with custom findByTitle
            Book savedBook = bookRepo.findByTitle("Harry Potter");
            //print title
            log.info(savedBook.getTitle());
            //print book author's full name
            log.info(savedBook.getAuthor().getFullName());
        };
    }
}

Notice how CommandLineRunner is used to run some code after the @SpringBootApplication starts. After creating a new book and author, the saved author is associated with the new book via:

book.setAuthor(author);

When we save the book, the foreign key column will populate for author_id.

Finally, we can fetch the saved book with our custom findByTitle() book repo method. Since the author is eagerly loaded, we can directly access the author and log the full name without a session.

You might also like: