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.
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 theAUTO_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 withfind
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 typeInteger
. -
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 |
...where x.age in ?1 |
NotIn | findByAgeNotIn(Collection |
...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) |