This is the ninth and the last part of my Spring Data JPA tutorial. Now it is time to take a look of what we have learned, and how we should use it to build better software.

Table of Contents

The contents of my Spring Data JPA tutorial is given in following:

The next step is to take a look of the advantages provided by Spring Data JPA and learn how we can use it in effective manner.

Promises Kept

The goal of the Spring Data JPA project is stated:

Implementing a data access layer of an application has been cumbersome for quite a while. Too much boilerplate code has to be written to execute simple queries as well as perform pagination, and auditing. Spring JPA aims to significantly improve the implementation of data access layers by reducing the effort to the amount that’s actually needed. As a developer you write your repository interfaces, including custom finder methods, and Spring will provide the implementation automatically.

This is a lot to promise. The question is, has Spring Data JPA achieved its goal. As you have learned from my tutorial, Spring Data JPA has following advantages over the “old school” method of building JPA repositories:

  • It provides CRUD capabilities to any domain object without the need of any boilerplate code.
  • It minimizes the amount of source code needed to write custom queries.
  • It offers simple abstractions for performing common tasks like sorting an pagination.

The thing is that implementing these functions have forced the developers to write a lot of boilerplate code in the past. Spring Data JPA changes all this. It minimizes the amount of code needed for implementing repositories.

Making It Work for You

I hate the term best practices because it has a negative effect on continuous improvement. However, I still feel that it is my responsibility to give you some guidance concerning the usage of Spring Data JPA. Here are my five cents about this matter:

Creating Queries

Your goal should be to use the Spring Data JPA to reduce the amount of code you have to write. With this goal in mind, I will you give some guidelines for creating queries with Spring Data JPA:

  • If the query can be build by using the query generation from method name strategy, I think you should use it. However, if the method name will become long and messy, I would consider using the @Query annotation in order to make the source code more readable.
  • Your second option for creating queries should be the @Query annotation and JPQL. This approach ensures that the you will not have to write more code than it is necessary.
  • Use JPA Criteria API or Querydsl only when you have no other options. Remember to extract the query generation logic into separate classes which creates Specification or Predicate objects (Depending on your technology selection).

JPA Criteria API Versus Querydsl

This is a question which should be asked by each developer. The usage of JPA Criteria API has been argued by claiming that you can use it to build type safe queries. Even though this is true, you can achieve the same goal by using the Querydsl. The first round ends in a draw, and we need to look for the answer from a bit deeper.

I will compare these two options in following categories: readability and testability.

Readability

Programs must be written for people to read, and only incidentally for machines to execute

– Abelson and Sussman on Programming.

With this guideline in mind, lets take a look of the implementations, which I created for my previous blog entries. The requirements of the search function are following:

  • It must be possible to search persons by using their last name as a search criteria.
  • The search function must return only such persons whose last name begins with the given search term.
  • The search must be case insensitive.

First, lets take a look of the implementation which is using the JPA Criteria API. The source code of my static meta model is given in following:

@StaticMetamodel(Person.class)
public class Person_ {
    public static volatile SingularAttribute<Person, String> lastName;
}

The source code of my specification builder class is given in following:

public class PersonSpecifications {

/**
     * Creates a specification used to find persons whose last name begins with
     * the given search term. This search is case insensitive.
     * @param searchTerm
     * @return
     */
    public static Specification<Person> lastNameIsLike(final String searchTerm) {
        
        return new Specification<Person>() {
            @Override
            public Predicate toPredicate(Root<Person> personRoot, CriteriaQuery<?> query, CriteriaBuilder cb) {
                String likePattern = getLikePattern(searchTerm);                
                return cb.like(cb.lower(personRoot.<String>get(Person_.lastName)), likePattern);
            }
            
            private String getLikePattern(final String searchTerm) {
                StringBuilder pattern = new StringBuilder();
                pattern.append(searchTerm.toLowerCase());
                pattern.append("%");
                return pattern.toString();
            }
        };
    }
}

Second, the source code of the implementations which uses Querydsl is given in following:

public class PersonPredicates {

public static Predicate lastNameIsLike(final String searchTerm) {
        QPerson person = QPerson.person;
        return person.lastName.startsWithIgnoreCase(searchTerm);
    }
}

This use case is pretty simple but it can still be used for demonstrating the differences of the JPA Criteria API and the Querydsl. The source code written by using Querydsl is clearly more readable than the one using the JPA Criteria API. Also, when the queries become more complex, the difference will be much bigger.

I would say that this round goes to Querydsl.

Testability

Software testability is the degree to which a software artifact (i.e. a software system, software module, requirements or design document) supports testing in a given context.

– Wikipedia.

In other words, the testability of your code defines the amount and quality of tests you can write at the same cost. If the testability of your code is high, you can write more tests with better quality than in a situation where the testability of your code is low.

Lets keep this measurement in mind when we will compare the unit tests written for implementations which were presented earlier.

First, lets check out the unit test for the implementation which uses the JPA Criteria API:

public class PersonSpecificationsTest {
    
    private static final String SEARCH_TERM = "Foo";
    private static final String SEARCH_TERM_LIKE_PATTERN = "foo%";
    
    private CriteriaBuilder criteriaBuilderMock;
    
    private CriteriaQuery criteriaQueryMock;
    
    private Root<Person> personRootMock;

@Before
    public void setUp() {
        criteriaBuilderMock = mock(CriteriaBuilder.class);
        criteriaQueryMock = mock(CriteriaQuery.class);
        personRootMock = mock(Root.class);
    }

@Test
    public void lastNameIsLike() {
        Path lastNamePathMock = mock(Path.class);        
        when(personRootMock.get(Person_.lastName)).thenReturn(lastNamePathMock);
        
        Expression lastNameToLowerExpressionMock = mock(Expression.class);
        when(criteriaBuilderMock.lower(lastNamePathMock)).thenReturn(lastNameToLowerExpressionMock);
        
        Predicate lastNameIsLikePredicateMock = mock(Predicate.class);
        when(criteriaBuilderMock.like(lastNameToLowerExpressionMock, SEARCH_TERM_LIKE_PATTERN)).thenReturn(lastNameIsLikePredicateMock);

Specification<Person> actual = PersonSpecifications.lastNameIsLike(SEARCH_TERM);
        Predicate actualPredicate = actual.toPredicate(personRootMock, criteriaQueryMock, criteriaBuilderMock);
        
        verify(personRootMock, times(1)).get(Person_.lastName);
        verifyNoMoreInteractions(personRootMock);
        
        verify(criteriaBuilderMock, times(1)).lower(lastNamePathMock);
        verify(criteriaBuilderMock, times(1)).like(lastNameToLowerExpressionMock, SEARCH_TERM_LIKE_PATTERN);
        verifyNoMoreInteractions(criteriaBuilderMock);

verifyZeroInteractions(criteriaQueryMock, lastNamePathMock, lastNameIsLikePredicateMock);

assertEquals(lastNameIsLikePredicateMock, actualPredicate);
    }
}

Second, the unit test for the implementation using Querydsl is given in following:

public class PersonPredicatesTest {
    
    private static final String SEARCH_TERM = "Foo";
    private static final String EXPECTED_PREDICATE_STRING = "startsWithIgnoreCase(person.lastName,Foo)";

@Test
    public void lastNameLike() {
        Predicate predicate = PersonPredicates.lastNameIsLike(SEARCH_TERM);
        String predicateAsString = predicate.toString();
        assertEquals(EXPECTED_PREDICATE_STRING, predicateAsString);
    }
}

After seeing the unit tests for both implementations, it should be obvious that writing unit tests for Querydsl is much easier than writing unit tests for the JPA Criteria API. Also, the unit test written to test the Querydsl predicate builder is much easier to understand. This is valuable because unit tests should also be used to document the behavior of the system.

At this point it should be clear that the winner of this round is Querydsl

PS. I am aware that unit tests do no ensure that the results returned by the created query are correct. However, I believe that they are still valuable because running unit tests is typically dramatically faster than running integration tests. It is still good to understand that in the context of integration testing, the testability of both implementations is equal.

Conclusions

The question is:

Should I use the JPA Criteria API or Querydsl?

It depends. If you are starting from scratch and you have a total control over your technology selections, you should at least consider using Querydsl. It makes your code easier to write and read. It also means that writing unit tests for your code is simpler and faster.

On the other hand, if you are modifying an existing system to use Spring Data JPA, and the existing code is using the JPA Criteria API, you might want to continue using it for the sake of consistency.

The fact is that there is no right answer for this question. The answer depends always from external requirements. The only thing you can do, is to ensure that you are aware of the different options, which are available to you. Only then you can choose the right tool for the task in hand.

There is Still More to Learn

The truth is that I have only scratched the surface of implementing JPA based repositories. I hope that the recommendations given in this blog entry will help you to take the first step, but I have to admit that there is a lot more to learn. I hope that the following resources will help you in your journey:

Reference Documentation

JPA Criteria API 2.0

Querydsl

Spring Data JPA Tutorial Part Nine: Conclusions(未翻译)的更多相关文章

  1. Spring Data JPA教程, 第二部分: CRUD(翻译)

    我的Spring Data Jpa教程的第一部分描述了,如何配置Spring Data JPA,本博文进一步描述怎样使用Spring Data JPA创建一个简单的CRUD应用.该应用要求如下: pe ...

  2. Spring Data JPA教程, 第八部分:Adding Functionality to a Repository (未翻译)

    The previous part of my tutorial described how you can paginate query results with Spring Data JPA. ...

  3. Spring Data JPA教程, 第七部分: Pagination(未翻译)

    The previous part of my Spring Data JPA tutorialdescribed how you can sort query results with Spring ...

  4. Spring Data JPA教程, 第六部分: Sorting(未翻译)

    The fifth part of my Spring Data JPA tutorialdescribed how you can create advanced queries with Spri ...

  5. Spring Data JPA教程, 第四部分: JPA Criteria Queries(未翻译)

    The third part of my Spring Data JPA tutorialdescribed how you can create custom queries by using qu ...

  6. Spring Data JPA教程,第一部分: Configuration(翻译)

    Spring Data JPA项目旨在简化基于仓库的JPA的创建并减少与数据库交互的所需的代码量.本人在自己的工作和个人爱好项目中已经使用一段时间,它却是是事情如此简单和清洗,现在是时候与你分享我的知 ...

  7. Spring Data JPA教程, 第五部分: Querydsl(未翻译)

    The fourth part of my Spring Data JPA tutorialdescribed how you can implement more advanced queries ...

  8. Spring Data JPA 教程(翻译)

    写那些数据挖掘之类的博文 写的比较累了,现在翻译一下关于spring data jpa的文章,觉得轻松多了. 翻译正文: 你有木有注意到,使用Java持久化的API的数据访问代码包含了很多不必要的模式 ...

  9. 快速搭建springmvc+spring data jpa工程

    一.前言 这里简单讲述一下如何快速使用springmvc和spring data jpa搭建后台开发工程,并提供了一个简单的demo作为参考. 二.创建maven工程 http://www.cnblo ...

随机推荐

  1. 最大流算法(Edmons-Karp + Dinic 比较) + Ford-Fulkson 简要证明

    Ford-Fulkson用EK实现:483ms #include <cstdio> #include <cstring> #define min(x,y) (x>y?y: ...

  2. BZOJ2482: [Spoj1557] Can you answer these queries II

    题解: 从没见过这么XXX的线段树啊... T_T 我们考虑离线做,按1-n一个一个插入,并且维护区间[ j,i](i为当前插入的数)j<i的最优值. 但这个最优值!!! 我们要保存历史的最优值 ...

  3. poj 1364 King(差分约束)

    题意(真坑):傻国王只会求和,以及比较大小.阴谋家们想推翻他,于是想坑他,上交了一串长度为n的序列a[1],a[2]...a[n],国王作出m条形如(a[si]+a[si+1]+...+a[si+ni ...

  4. 【JSP】JSP向MySQL写入|读出中文数据——乱码问题

    注意第14行

  5. HTML5全球普及加速:有望终结iOS与安卓界限【转】

    HTML5或将成为未来移动互联网时代的颠覆者. 5年前,当第一台iPhone诞生时,乔布斯断然拒绝了FlashPlayer,而选择HTML5(新型网页标准)取代Flash的功能.他曾预言,“虽然现阶段 ...

  6. 【转】AVL

    #include <iostream> #include <ctime> #include <queue> #include <cassert> #in ...

  7. 11、WebView 使用总结

    <WebView android:id="@+id/webview" android:background="@color/white" android: ...

  8. <译>Selenium Python Bindings 5 - Waits

    如今,大多数的Web应用程序使用AJAX技术.当页面加载到浏览器,页面中的元素也许在不同的时间间隔内加载.这使得元素很难定位,如果在DOM中的元素没有呈现,它将抛出ElementNotVisibleE ...

  9. 你今天Python了吗?(上)

    你今天Python了吗?为了提高你的生产效率,赶快去关注一下小蟒蛇的成长吧!别再把Python当作你的业余爱好了,她能为你做手头上几乎所有的工作,而且能做得更好,也让你把写代码看成是一种真正的乐趣.为 ...

  10. python学习之self,cls,staticmethod,classmethod

    一.总体说明 python类里会出现这三个单词,self和cls都可以用别的单词代替,类的方法有三种, 一是通过def定义的 普通的一般的,需要至少传递一个参数,一般用self,这样的方法必须通过一个 ...