Spring Data is a popular project which provides an easy-to-use and consistent, Spring-based programming model for storing and retrieving data from a data store. This allows you to connect with different data access technologies, relational and non-relational databases, map-reduce frameworks, and cloud-based data services.

Spring Data JPA (Java Persistence API) is an extension of Spring Data that makes it easy to connect with relational databases to implement JPA based repositories.

What is Java Persistence API?

Java Persistence API is a specification that defines an object-relational mapping (ORM) standard for storing, accessing, and managing Java objects in a relational database.

While originally intended for use with relational/SQL databases only, JPA's ORM model has been since extended for use with NoSQL data stores as well. At the moment, two most popular implementations of JPA's specification are Hibernate and EclipseLink.

Spring Data JPA is not a JPA provider but just an extra layer of abstraction on top of an existing JPA provider such as Hibernate. This means that it uses all features defined by the JPA specification such as the entity and association mappings, the entity lifecycle management, and JPA's query capabilities.

On top of this, Spring Data JPA defines its own cool features such as no-code repositories and the ability to generate queries based on method names. Thus, eliminating the need for writing too much boilerplate code for executing simple queries.

Why Spring Data JPA?

Although we can use any JPA implementation like Hibernate or EclipseLink directly in our project, using Spring Data JPA gives us additional benefits. This significantly reduces the boilerplate code and makes the overall development much faster.

The extra layer on top of the JPA specification also allows us to build Spring-powered applications that use JPA for data access layers.

Here is a list of features that makes the Spring Data JPA a go-to choice:

1. Repository Abstraction

The real strength of Spring Data JPA lies in the repository abstraction provided by the Spring Data Commons project. It hides the data store specific implementation details and allows you to write your business logic code on a higher abstraction level.

You only need to learn how to use Spring Data repository interfaces without worrying about the underlying implementation of the repository abstraction.

Spring Data Commons provides the following repository interfaces:

  • Repository — Central repository marker interface. Captures the domain type to manage as well as the domain type's id type.
  • CrudRepository — Interface for generic CRUD operations on a repository for a specific type.
  • PagingAndSortingRepository — Extension of CrudRepository to provide additional methods to retrieve entities using the pagination and sorting abstraction.
  • QuerydslPredicateExecutor — Interface to allow execution of QueryDSL Predicate instances. It is not a repository interface.

Spring Data JPA provides the following additional repository interfaces:

  • JpaRepository — JPA specific extension of Repository interface. It combines all methods declared by the Spring Data Commons repository interfaces behind a single interface.
  • JpaSpecificationExecutor — It is not a repository interface. It allows the execution of Specifications based on the JPA criteria API.

I'll discuss how to use these repositories in more detail in the next section as well as in the coming weeks. Here is a quick example of a repository that extends the CrudRepository interface:

public interface UserRepository extends CrudRepository<User, Long> {}

Things you can do by using the above UserRepository interface:

  • Persist, update and remove one or multiple User entities.
  • Find one or more users by their primary keys.
  • Count, get and remove all users.
  • Check if a User exists with a given primary key.

You might be wondering how it is possible when we didn't write any such method. The answer is the CrudRepository interface that defines all these methods, I mentioned before and makes them available for you.

2. Less Boilerplate Code

The best thing about Spring Data JPA is the default implementation of each method defined in its repository interfaces. This means that you do not need to write a lot of boilerplate code for CRUD methods. You only need to extend the JPA specific repository interface — JpaRepository and that's it. Spring Data JPA will ensure that CRUD methods are available for standard data access.

3. Auto-Generated Queries

Another cool feature of Spring Data JPA is the auto-generation of database queries based on method names. When you implement a Spring Data JPA repository interface, it analyses all the methods defined by the entity class and tries to generate the queries automatically from the method names.

Auto-generated queries may not be well-suited for complex use cases. But for simple scenarios, these queries are very useful. You just define a method on your repository interface with a name that starts with find...By. Spring then parses the method name and creates a query for it automatically.

Following is an example of a query that loads a User entity with a given name. Behind the scene, Spring generates a JPQL query based on the method name, sets the provided method parameters as bind parameter values, executes the query and returns the result:

public interface UserRepository extends CrudRepository<User, Long> {
    User findByName(String name);
}

Using Spring Data JPA with Spring Boot

As we have discussed above, Spring Data JPA makes the implementation of your data access layer much easier by reducing the boilerplate code. So that you easily build a Spring-based application using any data access technology.

In this section, I'll show you how to add and configure Spring Data JPA in a Spring Boot application using Hibernate as a persistence provider.

Follow the below steps to add Spring Data JPA support to your Spring Boot project.

Step 1 — Add Dependencies

You only need spring-boot-start-data-jpa dependency to enable JPA support in a Spring Boot application. Add the following dependency to your build.gradle file:

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'

If you are using Maven, add the following dependencies to your pom.xml file:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Spring Boot Starter Data JPA includes everything — all required dependencies and activates the default configuration. To start a Spring Boot application from scratch, use the Spring Initializr tool to easily bootstrap your application with required dependencies.

Step 2 — Configure Data Source

Spring Boot automatically configures Hibernate as a default JPA provider. So it's no longer required to explicitly configure the EntityManagerFactory bean unless we want to customize it.

Similarly, if you are using an in-memory database such as H2, HSQLDB, or Apache Derby, Spring Boot will auto-configure the DataSource bean for you as well. You only need to make sure that the corresponding database dependency is available in the classpath.

H2 Configuration

Just add the following H2 database dependency to your Gradle project's build.gradle file:

runtimeOnly 'com.h2database:h2'

For Maven, add the following dependency to your pom.xml file:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

That's it. Spring Boot will automatically configure the DataSource bean for you when it will detect the H2 database dependency in the classpath.

MySQL Configuration

If you want to use the MySQL database with Spring Data JPA, then you need to do a bit extra work. First of all, add the following MySQL driver dependency to your build.gradle file:

runtimeOnly 'mysql:mysql-connector-java'

For Maven project, include the following dependency to your pom.xml file:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

Next, you need to define the DataSource configuration. There are two ways to do this. You can either define it as a bean in a Java class with @Configuration annotation or use the Spring Boot properties file to define data source properties.

Here is how the Java-based data source configuration looks like in a Spring Boot project:

@Bean
public DataSource dataSource() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();

    dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
    dataSource.setUsername("root");
    dataSource.setPassword("rootpass");
    dataSource.setUrl("jdbc:mysql://localhost:3306/testdb?createDatabaseIfNotExist=true&useSSL=false");

    return dataSource;
}

To configure the data source using a properties file, you need to define the following properties in your application.properties or application.yml file:

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=rootpass
spring.datasource.url=jdbc:mysql://localhost:3306/testdb?createDatabaseIfNotExist=true&useSSL=false

Spring Boot will automatically configure a DataSource bean based on the above properties.

Step 3 — Define an Entity

Let us define a simple entity class for storing User objects:

User.java

package com.attacomsian.jpa.domains;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private String email;

    public User() {
    }

    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    // getters and setters removed for brevity
}

The above User class has three attributes (id, name, and email) and two constructors. The no-argument constructor is only required for the JPA. The other constructor is the one you should use to create instances of User to be saved to the database.

The User class is annotated with @Entity, indicating that it is a JPA entity. Since no @Table annotation is provided, it is assumed that this entity is mapped to a table named User.

The id property is annotated with @Id so that JPA recognizes it as the object’s ID. The id property is also annotated with @GeneratedValue to indicate that the ID should be generated automatically.

The other two properties, name and email, are left unannotated. It means that they are mapped to columns that have the same names as the properties themselves.

Step 4 — Create a Repository

The next step is to create a repository interface for the above User entity:

UserRepository.java

package com.attacomsian.jpa.repositories;

import com.attacomsian.jpa.domains.User;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface UserRepository extends CrudRepository<User, Long> {

    List<User> findByName(String name);

    User findByEmail(String email);
}

UserRepository extends the CrudRepository interface provided by the Spring Data Commons project. The type of entity and ID that it works with, User and Long, are specified in the generic parameters on CrudRepository. By extending CrudRepository, UserRepository inherits several methods for saving, deleting, and finding User entities.

Spring Data JPA also allows you to define other query methods by declaring their method signature. For example, UserRepository declares two additional methods: findByName() and findByEmail().

In a typical Java application, you have to write a class that implements UserRepository interface methods. However, it is no longer required with Spring Data JPA. It will create the repository implementation automatically, at runtime, from the repository interface. That is what makes Spring Data JPA so much powerful.

Step 4 — Create an Application Class

Let us create the Spring Boot main application class to play with the above UserRepository interface. Since we don't need the web part of Spring Boot, we will implement the CommandLineRunner interface to make it a console application.

Application.java

package com.attacomsian.jpa;

import com.attacomsian.jpa.domains.User;
import com.attacomsian.jpa.repositories.UserRepository;
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 userDemo(UserRepository userRepository) {
        return (args) -> {

            // create users
            userRepository.save(new User("John Doe", "john.doe@example.com"));
            userRepository.save(new User("Emma Watson", "emma.watson@example.com"));
            userRepository.save(new User("Seno Reta", "seno.reta@example.com"));
            userRepository.save(new User("Mike Hassan", "mike.hassan@example.com"));

            // fetch all users
            log.info("Users found with findAll():");
            log.info("---------------------------");
            for (User user : userRepository.findAll()) {
                log.info(user.toString());
            }
            log.info("");

            // fetch user by id
            User user = userRepository.findById(1L).get();
            log.info("User found with findById(1L):");
            log.info("-----------------------------");
            log.info(user.toString());
            log.info("");

            // fetch user by email address
            User userWithEmail = userRepository.findByEmail("john.doe@example.com");
            log.info("User found with findByEmail('john.doe@example.com'):");
            log.info("----------------------------------------------------");
            log.info(userWithEmail.toString());
            log.info("");

            // delete all users
            userRepository.deleteAll();

            // confirm users deletion
            log.info("Total users after deletion with :");
            log.info("--------------------------");
            log.info(userRepository.count() + " users are in DB");
            log.info("");
        };
    }
}

The above class is pretty much self-explanatory. We are using the @SpringBootApplication annotation on our main class to activate the auto-configuration. The main() method uses Spring Boot’s SpringApplication.run() method to launch an application.

We have also defined a CommandLineRunner bean method that gets UserRepository on runtime through dependency injection. Inside this method, we first create and save some users via the save() method. Next, we call findAll() to fetch all User objects from the database. Then it calls findById() and findByEmail() methods to get a single User by its ID and email address respectively. Finally, it calls deleteAll() to remove all users from the database. To verify that all users are successfully deleted, we call the count() method to get the number of entities.

Note: By default, Spring Boot enables JPA repository support and looks in the package (and its sub-packages) where the class with @SpringBootApplication annotation is located. If your application doesn't follow the default project structure, you need to configure your repositories package using the @EnableJpaRepositories annotation.

Step 5 — Run the Application

You can run the application from the command line through Gradle or Maven. If you use Gradle, you can run the application by typing:

$ ./gradlew bootRun

If you use Maven, you can run the application by running the following command:

$ ./mvnw spring-boot:run

Once the application is started, you should see the following output:

Users found with findAll():
---------------------------
User{id=1, name='John Doe', email='john.doe@example.com'}
User{id=2, name='Emma Watson', email='emma.watson@example.com'}
User{id=3, name='Seno Reta', email='seno.reta@example.com'}
User{id=4, name='Mike Hassan', email='mike.hassan@example.com'}

User found with findById(1L):
-----------------------------
User{id=1, name='John Doe', email='john.doe@example.com'}

User found with findByEmail('john.doe@example.com'):
----------------------------------------------------
User{id=1, name='John Doe', email='john.doe@example.com'}

Total users after deletion with :
--------------------------
0 users are in DB

Source code: Download the complete source code from GitHub available under MIT license.

Conclusion

In this article, I introduced you to Spring Data, Spring Data JPA, and Java Persistence API. We also talked about the core features of Spring Data JPA which make it such a powerful tool.

In the end, we created a simple application to demonstrate how to configure and use Spring Data JPA in a Spring Boot application for storing, accessing, and deleting objects in an in-memory H2 database.

Further Reading

You may be interested in the following Spring Data JPA articles:

✌️ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV. Buy me a coffee (cost $3)

Need help to start a new Spring Boot or MEAN stack project? I am available for contract work. Hire me to accomplish your business goals with engineering and design. Let’s talk about your project: hi@attacomsian.com.