Spring Data - Part 2: JPA - codecentric AG Blog

:

What happened before?

Part 1: Spring Data Commons
Part 2: Spring Data JPA

After looking at the Spring Data Commons project in the first part of this blog series, today I’m going to introduce you to the sub project Spring Data JPA.

JPA

Being a part of JEE stack, JPA is a standard API to persist Java objects into relational database systems. With the JPQL query language, database queries can be written independent of a specific SQL dialect. So understanding Spring Data JPA requires at least a basic knowledge of the JPA API.

Spring Data JPA

Based on Spring’s standard JPA Support, Spring Data JPA further simplifies writing JPA repositories (and much more). Usually one or more parameters have to be set before executing a JPQL query. To do so, the developer writes boilerplate code like this:

@Entity
@NamedQuery( name="myQuery", query = "SELECT u FROM User u where u.fullName = :fullName" )
public class User {
...
}
 
@Repository
public class ClassicUserRepository {
 
   @PersistenceContext EntityManager em;
 
   public List<User> findByFullName(String fullName) {
      TypedQuery<User> q = getEntityManger().createNamedQuery("myQuery", User.class);
 
      q.setParameter("fullName", fullName);
 
      return q.getResultList();
   }
   ...

@Entity @NamedQuery( name="myQuery", query = "SELECT u FROM User u where u.fullName = :fullName" ) public class User { ... }@Repository public class ClassicUserRepository {@PersistenceContext EntityManager em;public List<User> findByFullName(String fullName) { TypedQuery<User> q = getEntityManger().createNamedQuery("myQuery", User.class); q.setParameter("fullName", fullName); return q.getResultList(); } ...

This can be slightly reduced by using the fluent interface of a TypedQuery

@Repository
public class ClassicUserRepository {
 
   @PersistenceContext EntityManager em;
 
   public List<User> findByFullName(String fullName) {
      return getEntityManger().createNamedQuery("myQuery", User.class)
         .setParameter("fullName", fullName)
         .getResultList();
   }
   ...

@Repository public class ClassicUserRepository {@PersistenceContext EntityManager em;public List<User> findByFullName(String fullName) { return getEntityManger().createNamedQuery("myQuery", User.class) .setParameter("fullName", fullName) .getResultList(); } ...

… but still you are implementing a method that calls setters and executes the query for each and every single query. With Spring Data JPA the same query comes down to the following piece of code:

package repositories;
 
public interface UserRepository extends JpaRepository<User, String> {
 
   List<User> findByFullName(String fullName);
}

package repositories;public interface UserRepository extends JpaRepository<User, String> {List<User> findByFullName(String fullName); }

The basic idea is to derive all information that is required to execute a query from the signature of method declared in an interface(!). At runtime Spring injects a corresponding implementation that creates and executes the query via the JPA criteria API. This way, a very large subset of queries within a project can be written very fast and concise. The pattern can be combined with the common sorting and pagination features:

public interface UserRepository extends JpaRepository<User, String> {
 
   List<User> findByFullName(String fullName, Sort sort);
 
   List<User> findByFullName(String fullName, Pageable paging);
}

public interface UserRepository extends JpaRepository<User, String> {List<User> findByFullName(String fullName, Sort sort);List<User> findByFullName(String fullName, Pageable paging); }

With Spring Data JPA, JPQL queries don’t have to be declared as @NamedQuerys in the class file of the corresponding JPA entity. Instead a query is an annotation of the repository method(!):

	@Transactional(timeout = 2, propagation = Propagation.REQUIRED)
	@Query("SELECT u FROM User u WHERE u.fullName = 'User 3'")
	List<User> findByGivenQuery();

@Transactional(timeout = 2, propagation = Propagation.REQUIRED) @Query("SELECT u FROM User u WHERE u.fullName = 'User 3'") List<User> findByGivenQuery();

I really like this approach. The query is located in the place where it is executed and does not pollute the JPA entity itself. Separation of Concerns (SoC) at its best. A drawback is that you have to use a new Spring annotation @Query. Why didn’t they allow the use of @NamedQuery annotations?

A nice bonus is the validation of JPQL queries when the application context is assembled. This way JPQL syntax errors are detected as early as possible. Usually these errors are detected at query execution time.

Example

I provide a maven project holding all example source code at Github. The examples use OpenJPA as a JPA provider and the RDBMS HyperSQL DB. The unit test jpa.JpaRepoTest is a good place to start.

The most important configuration part is the one that defines the package structure of our repositorie interfaces to be automagically JPA-ified:

	<jpa:repositories base-package="jpa"/>

<jpa:repositories base-package="jpa"/>

If your are using more than one EntityManagerFactory, you have to specifiy which one should be used by your repositories.

Is there more?

Yes. As I said in the first part of this blog series, I’m just presenting some choosen aspects of Spring Data JPA. For a complete list of features, please check the project’s homepage.

What’s next?

Expect upcoming blog posts on Spring Data MongoDB or Spring Data Neo4J.