Spring in Practice

Willie Wheeler's Spring blog

Dynamic DAOs and Queries Using Spring Data JPA

| Comments

For a long time, creating a DAO layer in Spring has been a largely manual process:

  1. Create a base generic DAO interface.
  2. Create a generic abstract DAO implementation with general-purpose CRUD methods and common queries (e.g., findAll()).
  3. For each DAO we want, extend the base DAO interface with an entity-specific interface (e.g., CustomerDao).
  4. For each DAO we want, extend the abstract DAO class with an entity-specific concrete class (e.g., HibernateCustomerDao).

Items 1 and 2 amount to creating a homegrown DAO framework, and items 3 and 4 amount to using it to implement DAOs.

Now there’s a better way to do things.

The Spring Data family of projects provides a ready-made DAO framework. There are different projects, such as Spring Data JPA, Spring Data Neo4j and Spring Data MongoDB. Something they all have in common is that they provide framework code so we don’t have to implement it ourselves.

Moreover, Spring Data is able to generate concrete DAO implementations and custom queries automatically. So even step 4 above goes away in many cases. With Spring Data JPA you can create DAO tiers by defining interfaces.

In this post we’ll learn how to use Spring Data JPA to clean up our DAO tier. Let’s get the POM and configuration out of the way first. Then we’ll get into the actual repository code. We won’t get into the details of mapping actual entities (via JPA annotations or otherwise) as that’s outside the scope of what we want to cover here.

POM

First we need to choose which JPA provider and which package versions we want to work with. For the JPA provider, we’ll use Hibernate. For the package versions, we’ll use Spring 3.1.1, Spring Data JPA 1.0.3 and Hibernate 4.1.1 since those are current at the time I’m writing this.

Here are the relevant Maven dependencies for pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project ...>

    ...

    <!-- Use whatever versions make sense for your project. -->
    <properties>
        <hibernate.version>4.1.1.Final</hibernate.version>
        <spring.version>3.1.1.RELEASE</spring.version>
        <spring.data.version>1.0.3.RELEASE</spring.data.version>
    </properties>

    <dependencies>

        ... standard Spring dependencies (beans, context, core, etc.) ...

        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
            <version>${spring.data.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-core</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-ehcache</artifactId>
            <version>${hibernate.version}</version>
        </dependency>
    </dependencies>

    ...

</project>

JPA configuration

The JPA configuration goes here: /src/main/resources/META-INF/persistence.xml. Here’s the configuration itself. (Adjust as necessary if you’re not using MySQL.)

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
    version="1.0">
   
    <persistence-unit name="RunbookManager" transaction-type="RESOURCE_LOCAL">
        <description>This unit manages runbooks.</description>
        <provider>org.hibernate.ejb.HibernatePersistence</provider>
        <jta-data-source>jdbc/RunbookDS</jta-data-source>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5Dialect" />
            <property name="hibernate.show_sql" value="false" />
        </properties>
    </persistence-unit>
</persistence>

The configuration above is where we declare Hibernate as our JPA provider.

Now let’s look at the Spring configuration.

Spring configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xmlns:jpa="http://www.springframework.org/schema/data/jpa"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
        http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.0.xsd
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.1.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
        
    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/RunbookDS" resource-ref="true" />
    
    <bean id="entityManagerFactory"
        class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
        p:persistenceUnitName="RunbookManager"
        p:dataSource-ref="dataSource" />

    <bean id="transactionManager"
        class="org.springframework.orm.jpa.JpaTransactionManager"
        p:entityManagerFactory-ref="entityManagerFactory" />

    <jpa:repositories base-package="com.example.runbooks.repo" />
    
    <tx:annotation-driven />

    ... other beans ...

</beans>

Note the use of the jpa namespace to declare a package containing the repositories. This package contains the various interfaces we’re about to define. The <jpa:repositories> configuration tells Spring to scan for interfaces and create the repository implementations, magically.

Repository interfaces

OK, now we’ve made it to the good stuff. We’ll look at a few examples here.

To create a new DAO, we simply extend the JpaRepository interface, which is part of Spring Data JPA. It takes two type parameters: the relevant entity type, and its ID type. The interface comes with a bunch of standard CRUD operations and queries; see the JpaRepository API documentation for details on those.

Example 1: A barebones repo

First, the most barebones example would be where we’re perfectly happy with the standard CRUD and query operations that the Spring Data JpaRepository already provides. In that case, all we have to do is extend the interface and we’re done.

package com.example.runbooks.repo;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.runbooks.model.RunbookGroup;

public interface RunbookGroupRepo
    extends JpaRepository<RunbookGroup, Long> { }

With that simple interface definition, Spring Data JPA will be able to create an implementation dynamically that gives us methods like count(), findAll(), findOne(), save(), delete(), deleteAll(), deleteInBatch() and more for free.

Example 2. A simple custom query

Say we have a ChapterType entity with a key property, and we want a query that can find chapter types by key. No problem. We can use conventions around method names to tell Spring Data JPA which query we’d like to see:

package com.example.runbooks.repo;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.runbooks.model.ChapterType;

public interface ChapterTypeRepo
    extends JpaRepository<ChapterType, Long> {

    ChapterType findByKey(String key);
}

Spring Data maps the method to a query that effectively accomplishes

from ChapterType where key = :key

Example 3. A more complex custom query

The scheme from example 2 above extends to cases where the properties in question are complex, in a couple of different ways: first, they might involve multiple conditions in the “where” clause; second, they might involve joins. Suppose, for instance, that we want to find a chapter having a certain runbook ID and a certain chapter number. Suppose also that the JPQL would be

from Chapter c where c.runbook.id = :runbookId and
c.chapterType.number = :chapterNumber

Here’s how to build a repo supporting that query:

package com.example.runbooks.repo;

import org.springframework.data.jpa.repository.JpaRepository;
import com.example.runbooks.model.Chapter;

public interface ChapterRepo
    extends JpaRepository<Chapter, Long> {

    Chapter findByRunbookIdAndChapterTypeNumber(
        Long runbookId, Integer chapterNumber);
}

The convention implicit in the method name is fairly obvious, especially in light of the JPQL query. The convention can admittedly lead to some awkward method names, as it does in this case. (A better name might be findByRunbookIdAndChapterNumber().) But the convenience is tough to beat.

Interested in learning more about Spring Data JPA? See my post Pagination and sorting with Spring Data JPA.

Comments