Guide to Spring Data JPA

What is Spring Data JPA?

Spring Data JPA is a part of the Spring Data family.

Let's talk about what Spring Data JPA is and some of the features we're going to cover in this article. First off, this framework builds upon the popular and powerful Spring framework and is considered one of the core projects in Spring's suite of tools.

Spring Data JPA also builds upon and enhances JPA, which stands for "Java Persistence API". Most applications are backed with some kind of data store. As your application complexity and feature set grows, you'll find that your Data Access Layer and persistence tier code will also grow.

One of the core objectives of Spring Data JPA is to reduce your code and simplify your Data Access Layer, while still maintaining a rich and full-featured set of functionality. To make this possible, Spring Data JPA allows you to build intelligent Spring Repository stereotyped interfaces.

These Repositories are Java interfaces that allow you as the developer to define a data access contract. The Spring Data JPA framework can then inspect that contract, and automatically build the interface implementation under the covers for you.

For Spring Data JPA to intelligently generate an implementation of your Repository interface, a Query DSL is needed.

DSL is an acronym for Domain Specific Language. The Query Domain Specific Language allows you to create Java interface methods that utilize certain keywords along with JPA entity attributes to perform the work needed to correctly implement your queries without having to provide much in the way of actual coding. We will also cover just about all you need to know about Query DSL specifics.

And last, Spring Data JPA provides some nice extras that are often seen and used in Data Access Layers in persistent tiers. Features such as Auditing, paging, and handling of native SQL queries can be utilized with Spring Data JPA framework. If for some reason, Spring Data JPA can't provide a solution for one of your Data Access Layer needs, it can easily get out of the way and let you code or work side-by-side, or outside of the framework entirely, without stepping on your toes.

Before Getting Started

Before we get into more detail with Spring Data JPA, I want to talk about what this article will not cover. First, we will not be going in depth with JPA and ORM, or Object Relational Mapping concepts.

In fact, these topics are large enough that they warrant their own courses and tutorials. We also won't be going in-depth on Relationships, like one-to-many, many-to-many, many-to-one, and so on. Those topics are covered well in the other JPA courses and tutorials. We also won't be going into SQL, JDBC, JPAQL, and NoSQL structures.

In this article, when we talk about the Spring Data JPA Query DSL, we will utilize JPAQL, so having a foundational knowledge of SQL and JPAQL will definitely be beneficial. And last, we won't be covering Core Spring concepts like Dependency Injection, the Spring context and container, and basic Spring configuration.

We also will be covering a few code examples to gain experience and understanding of Spring Data JPA throughout this article.

You're going to need tools like Java, Maven, and an IDE (IntelliJ, Eclipse, or NetBeans) to set up on your development machine to get the most out of this article.

Choosing a Java Data Access Layer

Whenever you are building or working on a Data Access Layer or persistence tier, you have a variety of options you can use. I want to take a minute to talk about these options to help you see where Spring Data JPA can fit in architecturally. You should also realize that no one framework or API typically works for everything. And the best Data Access Layers are often a hybrid of frameworks.

If you're working with a really simple database with maybe only a few tables or you have a lot of Native SQL needs, then some Data Access Layer frameworks can be an overkill. Using straight JDBC or Spring JDBC with Native SQL may be your best and simplest option. Sometimes your reporting needs to dictate a certain Data Access Layer, and JDBC or Native SQL may work best for that.

If you have an application that needs to perform a lot of SQL inserts, updates, or deletes, you'll want to get a framework that specializes in that particular functionality. JPA is not a great candidate for massive amounts of writes to your data store. The reason why JPA or ORMs in general struggle with large writes is that the nature of the framework requires that you create your object's graph in memory, then update it with the changed values and then persist it back to your data storage.

If you're working with really large graph trees, this can be quite expensive time-wise, and end up creating large memory footprints on your server. Instead, you should probably be looking at a framework that handles batching specifically. For example, a framework like Spring Batch or Hadoop. Java EE 7 also contains a Batch writing component as part of its core functionality now. Make sure to take everything into account when you are building your initial architecture and stack for your Java application.

Installing Spring Data JPA

Let's go ahead and get Spring Data JPA installed and configured. First, we're going to need to add the Spring Data JPA dependency into our application class-path.

Since we're using Maven to handle our dependencies, we can add this dependency block into our pom.xml file.

Next, you'll need to tell Spring to configure and load the JPA repositories. This is where most of the magic of Spring Data JPA actually occurs. This step in installing Spring Data JPA is where you get your repository interface implemented under the covers when your app starts up. If you're using Spring XML configuration, then you need to add this jpa:repositories declaration into your application context XML file for example: <jpa:repositories base-package="com.demo.repositores"/>.

The base-package attribute tells Spring Data JPA which packages it should scan to look for JPA repositories. You should set the base-package to your project's root package structure, or a package that is known to contain your JPA repositories.

The other way that you can configure Spring Data JPA is to use the @EnableJpaRepositories annotation. This is the preferred way if you're using Spring Boot or a Java configuration with Spring rather than XML configuration.

Spring Repositories

Spring has supported the concept of a repository for some time now. Repository is one of Spring's core stereotypes and you should plan on using them in your data access layer, regardless of your chosen data access layer API and framework.

The repository's whole point is to define a contract that your data access layer will implement. This contract, or rather interface, can then be included and bound to by client code that needs to access data in some fashion. What this really means is that a Spring repository is essentially an implementation of the Data Access Object pattern.

By defining an interface that the surface code uses, the data access layer is free to implement the DAO contract anyway.

That may mean that when you started your project you implemented your data access layer with JPA. Maybe at some point later in the project, you needed to replace that implementation with the JDBC implementation instead of JPA. When you switch the interface implementation out, the client service code didn't even notice or care that anything changed implementation-wise in your data access layer. And who knows, maybe at some point in the future, you'll need to switch out your JDBC implementation with something else. This pattern allows you to set up hybrid data access layers.

Your implementation may actually do some operations using JPA while utilizing JDBC for other operations. The purest definition of a DAO pattern would say that you need to define a contract with an interface. Spring repositories, however, don't necessarily need to be an interface.

Repository Architectural Overview

Repositories fit into the data access layer but they aren't the only objects and concepts that you have to keep in mind when working on a server side. Let's look at a typical Spring application from an architectural point of view to see how everything might fit together.

Your database typically consists of one or more tables. They may or may not be related such as a parent or child relationship. These structures all live in the database which is typically a standalone server separate from your application code and server.

As we move into our data access layer, we have JPA entities mapped to database tables. The entities map one to one with a JPA repository. By keeping the repository focused on a single entity, it keeps the DAO pattern limited to that specific data and data structure.

With standard Spring repositories, you don't have to follow this standard. You can technically have the repository access anything and everything on the data side. But with Spring data JPA repositories, the repository is limited to a single JPA entity.

Spring services can then be used to perform logical bundles of work for the application. Spring's @Service annotation is another Spring stereotype and you would use it on classes and interfaces that live in your service layer.

And last, your application will typically have some kind of controller layer that handles request routing coming in from the UI. These controllers can utilize one or more services and are responsible for returning a response to the UI or presentation tier.

Note: The important thing to remember is that your code dependencies and bindings should only move to the right in this diagram. So controllers can inject services or repositories and services can inject repositories, but services and repositories should never inject controllers.

Spring Data JPA Repositories

You're starting to see that standard Spring repositories and Spring Data JPA repositories differ slightly in concept and structure.

Here are the main differences:

  • Java interface instead of a class
  • Map 1 to 1 with a JPA entity
  • Focus on DAO contract

First, all JPA repositories are Java interfaces instead of classes. These interfaces are associated with a JPA entity. Each JPA repository can only perform data access operations for that particular entity and its data attributes. This helps focus the JPA repository on the DAO contract for that entity and its backing data. How do JPA repositories tie to a particular JPA entity? This is accomplished by using Java generics and typing:

public interface MyJpaRepository extends JpaRepository<Entity, Id Type> {}

By supplying the JPA entity and its primary key data type, the JPA repository now knows exactly what database table in columns it can work with because all of that information is bundled nicely inside your JPA entity.

The last big difference between Spring Data JPA repositories and standard Spring repositories is how the implementation happens to fulfill the DAO pattern.

The DAO pattern allows you to implement the DAO contract however you want, and that implementation is up to you. With Spring Data JPA repositories, we no longer care about the implementation details since the framework is going to provide that for us. This lets us as the developer focus on the DAO contract while fulfilling Spring Data JPA's goal of simplifying our data access layer without any loss of functionality.

The big takeaway that you need to remember is that when your application starts up, Spring Data JPA recognizes your JPA repository and automatically generates an implementation for the DAO contract that's specified in that interface.

JpaRepository Features

When you extend the JPA repository interface, you get access to a bunch of other features as well. The functionality that comes with the JPA repository includes the CRUD operations that you will see later in the code examples and it also contains Query DSL functionality which we will cover later in the article.

Functionality

  • Query DSL
  • CRUD operations
  • Paging and sorting
  • Helpers
    • count()
    • exists(Long id)
    • flush()
    • deleteInBatch(Iterable entities)

There's also paging and sorting capabilities, and last, the JPA repository contains a few helpers that can make working with your data access layer much easier. Some of these include finding the count of your backing DB table, testing whether a record exists in the database, flushing your persistence context changes to the database, and handling deleting multiple entities with a single query using the handy deleteInBatch() method.

If you take a look at the interface hierarchy of the JPA repository, you'll see that there are three more parent interfaces that the JPA repository extends from.

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

You'll see that when combined into one hierarchical structure, all of the functionality that we've talked about for the JPA repository begins to make sense. The nice thing about breaking functionality out into separate interfaces is that it gives you the opportunity to reduce functionality in your data access layer if needed.

Maybe you only want to have CRUD operations available on your repository so in that case, you can simply extend the CRUD repository instead of JPA repository. One final thing to note about the JPA repository hierarchy is that the JpaRepository interface is the only interface in the Spring Data JPA project. The other three interfaces actually come from the core Spring data project.

Code Example

In this section, we are going to create a simple Spring Boot example so we can implement Spring Data JPA and the REST inside our application.

Choose your favorite IDE, (e.g. Eclipse and IntelliJ IDEA have embedded Spring Initializr for setup dependencies). To generate Spring Boot project you can refer to Spring Initializr as well to bootstrap your application with dependencies.

In the pom.xml file, we added few more dependencies for our simple project - such as spring-web which provides us with Spring MVC and Spring Rest, H2 database and JPA:

<dependencies>

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

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

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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>

We created a controller class named UserController which contains the @RestContoller annotation. This annotation tells Spring MVC that this is the Controller and it is having a rest endpoint. It's practically the equivalent of writing both @Controller and @ResponseBody.

The controller also contains a @RequestMapping("/users") for mapping an HTTP request to a method or a class, a GET method, a POST method, and an @Autowired UserJpaRepository object.

@RestController
@RequestMapping("/users")
public class UserController {

    @Autowired
    private UserJpaRepository userJpaRepository;

    @GetMapping(value = "/all")
    public List<Users> getAll(){
        return userJpaRepository.findAll();
    }

    @PostMapping(value = "/load")
    public Users load(@RequestBody final Users users) {
        return userJpaRepository.save(users);
    }
}

Now how do we get the data from the database? Let's jump into the definition of the repository interface UserJpaRepository which extends 'JpaRepository'.

Inside JpaRepository<Users, Long> we passed the model and its Id. In the controller example, we're using findAll() to get all the records from the database and 'save()' to save them.

public interface UserJpaRepository extends JpaRepository<Users, Long> {}

The Users model class will be our entity. The class itself is annotated with @Entity, the id variable is annotated with @Id and @GeneratedValue.

  • The @Entity annotation will map this POJO into the database with all of its fields.
  • The @Id annotation marks the field as the primary key of the table.
  • The @GeneratedValue annotation practically sets the AUTO_INCREMENT option of the primary key to true. You can optionally add (strategy = GenerationType.AUTO) to achieve this.
@Entity
public class Users {

    @Id
    @GeneratedValue
    private Long id;

    private String name;
    private Integer salary;

    // getters and setter
}

After you start the application, navigate to 'localhost:8080/users/all' to get all users, and you should be receiving nothing as you can see on the image below because you don't have any users in the H2 memory database.

Next, go to your favorite REST Client tool (image below shows example of Postman). As you can notice we're using the POST method from our controller which will save the data.

We added name and salary and sent the POST request. The id is auto-generated as you can see in the response body.

The application responded with a Status 200 OK. Everything is working as it should! This way, you can add as many users as you want.

Note: After you restart the application, all data will be lost because we're using an in-memory database.

Now go to localhost:8080/users/all again to GET all user records from the database and you should be greeted with:

Query DSL Overview

Of all the features Spring Data JPA provides, the Query DSL feature in the JPA repository is one of the most powerful, flexible, and pertinent to your application's data access query and read needs.

Because the Query DSL is extremely customizable and is based on your JPA entity, it can also be one of the harder aspects of Spring Data JPA to pick up and become efficient with.

Advantages of Using a Query DSL

Some of the advantages of using a Query DSL is that it will give you overwriting custom queries and finders.

First, think about all of the efforts that you've spent mapping JPA entities to your database tables. If you have a large database schema, setting up your JPA entities can take some work. Your entity layer contains loads of information about the database tables that it maps to.

For example, JPA knows the table name, the columns, and the columns' data types all by looking at your entity annotations, attributes, and attribute data types. If you've gone the extra mile with your entity mapping, you can specify constraints in relationships which give you even more knowledge about your database from the software level. Why throw all this knowledge away to have to implement queries and finders manually?

Let a framework like Spring Data JPA use this info so you can just define the query contract and let the framework provide the implementation. Because we aren't adding implementation code, that frees us as application developers from having to maintain that code.

Over time, it collects tools and other various items and after a while, you'll find yourself cleaning, decluttering, and organizing your garage on a Saturday. So, from an application development standpoint, don't waste your precious Saturday time cleaning out your garage. Let Spring Data JPA deal with your implementation mess while you go fishing or do something else.

Another time-saving advantage of using the Spring Data JPA Query DSL is that the framework checks the validity of your queries when your application starts up, rather than at run-time. This saves time from having to actually find and test the point in your application that the query has called.

Application start-up checks also safeguard against refactoring changes. If an entity attribute changes, you'll quickly know if that broke any of your queries when you start up your application.

Last, Query DSLs have been in use in scripted language platforms for a long time now. Ruby on Rails' active record framework or Django's ORM stack are both good examples of this. Java has been slow to adopt this methodology because of its compiled and type-check nature. It's easy to add functionality on the fly in a scripted language because the clients that use it aren't type-checked or compiled.

This gives scripted languages a lot of flexibility in this particular area. Spring Data JPA has found a pretty good balance by requiring the developer to define the data contract, and then the framework can implement that contract much like Rails or Django would. Client code can then bind and compile against that interface contract.

And before going any further, let's make sure we're clear on what a DSL is. DSL is an acronym for Domain Specific Language. This is a term used to classify an extension of a programming language to address a domain. In Spring Data JPA's case, this means that the framework is enhancing Java to be better suited at creating and working with JPA queries.

We use domain specific language in speech all the time. Doctors have terms and words that help them work more efficiently, and the same for lawyers or construction workers, or any industry. The Spring Data JPA Query DSL is simply all about defining terms and syntax to work with JPA queries more efficiently.

Query Method Syntax

Let's go over the basics of the syntax needed to make these query methods work. First, query methods are simply methods defined in your JPA repository that Spring Data JPA will auto-implement on your behalf. They are one way that Spring Data JPA can implement queries for you.

When you create a query method, the query parser will look for methods that start with find, query, read, count, or get. These prefixes can be enhanced with other keywords until, eventually, you get to the B-Y, or By, a section of the method name.

This signals that the criteria, or filter piece, of the query, is beginning and Spring Data JPA matches up the entity attributes of the method criteria with the actual WHERE clause in your SQL Multiple criteria definitions can be added to your method name with the And or Or keywords.

This may sound a little confusing, so let's look at the location query on the code below.

public interface LocationJpaRepository extends JpaRepository<Location, Long> {
    findByAgeLike(Integer age);
}
  • find - The method starts with find so that the query parser understands that it needs to implement this query contract.

  • By - Following the previous keyword, we added this one signaling that the criteria information will be coming next in the method name.

  • Age - Afterwards, we specified it further. Age matches the attribute name age in my location JPA entity, and age is of data type Integer.

  • Like - The final keyword tells the implementation that we want to create a Like query, rather than an exact match.

I then pass in an Integer variable that the query implementation should use as the actual filter criteria. It's of type Integer because our data type of age in the location entity is of type Integer.

By piecing the query DSL keywords together with the JPA repository generics typing, you can see how Spring Data JPA can generate the JPQL for us.

This, in turn, gets mapped to the actual SQL that will get issued against the database thanks to the JPA ORM framework.

Keywords

Keyword Sample JPQL Snippet
And findByLastnameAndFirstname ...where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname ...where x.lastname = ?1 or x.firstname = ?2
Is, Equals findByFirstnameEquals ...where x.firstname = ?1
Between findByStartDateBetween ...where x.startDate between ?1 and ?
LessThan findByAgeLessThan ...where x.age < ?1
LessThanEqual findByAgeLessThanEqual ...where x.age <= ?1
GreaterThan findByAgeGreaterThan ...where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual ...where x.age >= ?1
After findByStartDateAfter ...where x.startDate > ?1
Before findByStartDateBefore ...where x.startDate < ?1
IsNull findByAgeIsNull ...where x.age is null
IsNotNull, NotNull findByAge(Is)NotNull ...where x.age not null
Like findByFirstnameLike ...where x.firstname like ?1
NotLike findByFirstnameNotLike ...where x.firstname not like ?1
StartingWith findByFirstnameStartingWith ...where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith ...where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining ...where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc ...where x.age = ?1 order by x.lastname desc
Not findByLastnameNot ...where x.lastname <> ?1
In findByAgeIn(Collection ages) ...where x.age in ?1
NotIn findByAgeNotIn(Collection ages) ...where x.age not in ?1
True findByActiveTrue() ...where x.active = true
False findByActiveFalse() ...where x.active = false
IgnoreCase findByFirstnameIgnoreCase ...where UPPER(x.firstame) = UPPER(?1)
Last Updated: July 27th, 2023
Was this article helpful?

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms