Automatic JPA repositories with Spring Data

By Craig Walls, author of Spring in Action, Fourth Edition

Spring provides support for several persistence frameworks, including Hibernate, iBATIS, Java Data Objects (JDO), and the Java Persistence API (JPA). This article, based on chapter 11 of Spring in Action, discusses Spring Data JPA.

As with Spring's JDBC support, Spring's support for ORM frameworks provides integration points to the frameworks as well as some additional services:

Once you get the hang of using one ORM framework with Spring, you'll find it easy to switch to another one. In this article we'll look at the Spring Data project by looking at Spring Data JPA.

To start, let's reexamine the addSpitter() method:

public void addSpitter(Spitter spitter) {
    entityManager.persist(spitter);
  }

In any reasonably-sized application, you're likely to write that same method, almost exactly the same, many times. In fact, aside from the fact that it's a Spitter that's being persisted, I'll be you've written a very similar method before. And the other methods in JpaSpitterRepository aren't all too innovative, either. The domain types will be different, but those methods are fairly common across all kinds of repositories.

Why do we keep writing the same persistence methods over and over again, just because we're dealing with different domain types?

Spring Data JPA brings an end to this boilerplate madness. Rather than write the same repository implementations again and again, Spring Data lets you stop at writing the repository interface. No implementation is required. For instance, take a look at the SpitterRepository interface in listing 1.

Listing 1 Spring Data helps you create repositories from only an interface definition.

public interface SpitterRepository
         extends JpaRepository {
}

At this point, SpitterRepository doesn't appear all that useful. But there's a lot more here than meets the eye.

The key to writing a Spring Data JPA repository is to extend one of a handful of interfaces. Here, SpitterRepository extends Spring Data JPA's JpaRepository (we'll mention a few of the other interfaces in a moment). In doing so, JpaRepository is parameterized such that it knows that this is a repository for persisting Spitter objects and that Spitters have an ID of type Long. It also inherits 18 methods for performing common persistence operations such as saving a Spitter, deleting a Spitter, and finding a Spitter by its ID.

At this point, you might be expecting that the next step is to write a class that implements SpitterRepository and its 18 methods. If that were true, then this chapter is about to take a very boring a tedious turn. Fortunately, however, we won't be writing any implementations of SpitterRepository. Instead, we'll let Spring Data do it for us. All we need to do is ask.

To ask Spring Data to create an implementation of SpitterRepository, we need to add a single element to our Spring configuration. Listing 2 shows the XML configuration needed to put Spring Data JPA into motion.

Listing 2 Configuring Spring Data JPA

package com.habuma.spitter.db;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

@Configuration
@EnableJpaRepositories("com.habuma.spitter.db")
public class SpringDataJpaConfig {
}

The @EnableJpaRepositories annotation unleashes all of the magic of Spring Data JPA. Much like the @ComponentScan annotation, @EnableJpaRepositories is given a base package to scan. But whereas @ComponentScan scans a package (and its subpackages) for classes that are annotated with @Component, @EnableJpaRepositories scans its base package for any interfaces that extend Spring Data JPA's Repository interface. When it finds any such interface, it will automatically (at application startup time) generate an implementation of that interface.

Getting back to our SpitterRepository interface, it extends JpaRepository. As illustrated in figure 1, JpaRepository extends the marker Repository interface (albeit indirectly). Therefore, SpitterRepository transitively extends the Repository interface that @EnableJpaRepositories is looking for. When @EnableJpaRepositories finds it, it will create an implementation of SpitterRepository, including an implementation of all 18 of the methods inherited from JpaRepository, PagingAndSortingRepository, and CrudRepository.

Figure 1 Spring's data access template classes take responsibility for the common data access duties. For the application-specific tasks, it calls back into a custom callback object.

It's important to understand that the repository implementation will be generated at application startup time, as the Spring application context is being created. It is not the product of build-time code generation. Nor is it created the the time that any of the interface's methods are called.

Nifty, huh?

It's pretty awesome that Spring Data JPA was able to give us 18 convenient methods for common JPA operations on Spitter objects without us having to write that persistence code ourselves. But what if we need something more than what those 18 methods offer? Fortunately, Spring Data JPA offers a few ways to add custom methods to a repository. Let's see how to define a custom query method using Spring Data JPA.

Defining query methods

One thing our SpitterRepository will need is a means of looking up a Spitter object given a username. For example, let's say we modify the SpitterRepository interface to look like this:

public interface SpitterRepository
       extends JpaRepository<Spitter, Long> {
    Spitter findByUsername(String username);
}

The new findByUsername() is simple enough and should satisfy our requirement. Now, how do we get Spring Data JPA to incorporate an implementation of that method?

Actually, there's nothing else that needs to be done to implement findByUseranme(). That's because the method signature tells Spring Data JPA everything it needs to know in order to create an implementation for the method.

When creating the repository implementation, Spring Data will examine any methods in the repository interface, parse the method name, and attempt to understand the method's purpose in the context of the persisted object. In essence, Spring Data defines a sort of miniature domain specific language (DSL) where persistence details are expressed in repository method signatures.

Spring Data knows that this method is intended to find Spitters, because we parameterized JpaRepository with Spitter. The method name, "findByUsername", makes it clear that this method should find Spitter's by matching up their username property with the username passed in as a parameter to the method. Moreover, since the signature defines the method as returning a single Spitter and not a collection, it knows that it should look for only one Spitter whose username matches.

The findByUsername method is simple enough, but Spring Data can handle even more interesting method names as well. By assembling object properties and the keywords from table 1 you can define a wide range of query methods.

Table 1 The keywords of Spring Data's method-name DSL.

DistinctBetweenWithinNear
LessThanGreaterThanIsNull/NullIsNotNull/NotNull
LikeNotLikeInNotIn
AndOrNotOrderBy

For example, what if we wanted to find all Spitters where the username matches or the fullname is a partial match? To do that, we just need to define a new findByUsernameOrFullNameLike() method:

public interface SpitterRepository
     extends JpaRepository { 
  Spitter findByUsername(String username); 
  List<Spitter> findByUsernameOrFullNameLike(
                                    String username, String fullName);
}

Again, this method is for finding Spitters. But this time, we're expecting more than one result, so the method returns a List. And I've tossed in a few special keywords such as "Or" and "Like" to guide Spring Data in creating the implementation.

This is just a taste of the kind of methods you can declare and have Spring Data JPA implement for you. Spring Data JPA can make by carefully constructing a repository method signature using a mix of property names and keywords, you can make Spring Data JPA generate an implementation method to query for almost anything you can imagine.

Nevertheless, Spring Data's mini-DSL has its limits and there may be times when it isn't convenient or even possible to express the desired query in a method name. When that happens, Spring Data has you covered with its @Query annotation.

Declaring custom queries

Suppose that you want to create a repository method to find all Spitters whose email address is a GMail address. One way to do this is to define a findByEmailLike() method and pass in "%gmail.com" anytime you want to find GMail users. But it'd be nice to define a more convenient findAllGmailSpitters() that doesn't require that the partial email address be passed in:

List findAllGmailSpitters();

Unfortunately, this method name doesn't adhere to Spring Data's method naming conventions. When Spring Data attempts to generate an implementation for this method, it will not be able to match up the contents of the method name with the Spitter metamodel, and will throw an exception.

In situations where the desired data can't be adequately expressed in the method name, you can use the @Query annotation to provide Spring Data with the query that should be performed. For the findAllGmailSpitters() method, you might use @Query like this:

@Query("select s from Spitter s where s.email like '%gmail.com'") 
List<Spitter> findAllGmailSpitters();

Here, we still didn't write the implementation of the findAllGmailSpitters() method. We only gave the query, hinting to Spring Data JPA about how it should implement the method.

As we've seen here, @Query is useful when it's difficult to express the query we want using the method naming convention. Another time it may be useful is when, by following the naming convention, the method name would be incredibly long. For example, consider this finder method:

List<Order>
  findByCustomerAddressZipCodeOrCustomerNameAndCustomerAddressState();

Now that's a method name!

I'll grant that this is a contrived example. However, it wouldn't be out of the question for there to a real world need to write a repository method to perform a query that can be defined using a really long method name. In that situation, you probably would rather come up with a shorter method name and use @Query to specify how the method should query the database.

The @Query annotation is handy for adding custom query methods to a Spring Data JPA-enabled interface. But it's limited to a single JPA query. What if you need to mix in something more complex than what can be handled in a simple query?

Mixing in custom functionality

It's very likely that at some point you'll want some functionality in your repository that simply can't be described with Spring Data's method naming conventions or even with a query given in the @Query annotation. As awesome as Spring Data JPA is, it still has its limits and you may find yourself needing to write a repository method the old-fashioned way: By working with the EntityManager directly. When that happens, do you just give up on Spring Data JPA and go back to writing your repositories?

In short, yes. When you need to do something that Spring Data JPA can't do, then yes, you'll need to work with JPA at a lower level than what Spring Data JPA offers. But the good news is, you don't have to give up on Spring Data JPA completely. You only need to work at the lower level for those methods that require it. You can still let Spring Data JPA do the grunt work for the stuff it knows how to do.

When Spring Data JPA generates the implementation for a repository interface, it also looks for a class whose name is the same as the interface's name postfixed with "Impl". If the class exists, Spring Data JPA will merge its methods with those that are generated by Spring Data JPA. For the SpitterRepository interface, the class it looks for will be named SpitterRepositoryImpl.

To illustrate, suppose that we need a method in our SpitterRepository that updates all Spitters who have posted 10,000 or more Spittles, setting them to "Elite" status. There's no good way to declare such a method using Spring Data JPA's method naming conventions or with @Query. The most practical way to do it is using the eliteSweep() method in listing 3.

Listing 3 A repository implementation that supports promoting frequent Spitters to Elite status.

public class SpitterRepositoryImpl implements SpitterSweeper {
  @PersistenceContext 
  private EntityManager em;

  public int eliteSweep() {
    // TODO: ADD IMPLEMENTATION HERE
  }
}

There's nothing particularly special about SpitterRepositoryImpl. It simply uses the injected EntityManager to do its work. Notice that SpitterRepositoryImpl doesn't implement the SpitterRepository interface. Spring Data JPA is still responsible for implementing that interface. Instead, SpitterRepositoryImpl implements SpitterSweeper, which looks like this: The only thing that ties it into our Spring Data-enabled repository is its name.

public interface SpitterSweeper{
    int eliteSweep();
}

We'll also want to make sure that the eliteSweep() method is declared in the SpitterRepository interface. The easy way to do that and avoid duplicating SpitterRepository code is to change SpitterSweeper so that it extends:

public interface SpitterRepository
       extends JpaRepository<Spitter, Long>, 
               SpitterSweeper {
    ....
}

As I mentioned, Spring Data JPA associates the implementation class with the interface because the implementation's name is based on the name of the interface.

The "Impl" postfix is only the default, though. If you'd prefer to use some other postfix, you simply need to specify it when configuring <jpa:repositories> by setting the repository-impl-postfix attribute:

<jpa:repositories base-package="com.habuma.spitter.db" 
    repository-impl-postfix="Extra" />

With repository-impl-postfix set to "Extra," Spring Data JPA will look for a class named SpitterRepositoryExtra to match up with the SpitterRepository interface.

Summary

We got our first taste of Spring Data by seeing how to declare JPA repository interfaces while letting Spring Data JPA automatically generate implementations of those interfaces at runtime. And when we needed more out of those repository methods than what Spring Data JPA could handle on its own, we were able to help it out with the repository method implementationsand by writing custom @Query annotation.

Here are some other Manning titles you might be interested in:


Spring Batch in Action

Spring Batch in Action
Arnaud Cogoluegnes, Thierry Templier, Gary Gregory, Olivier Bazoud

Spring in Practice

Spring in Practice
Willie Wheeler, John Wheeler, and Joshua White

Spring Integration in Action

Spring Integration in Action
Mark Fisher, Jonas Partner, Marius Bogoevici, and Iwein Fuld