博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring Data JPA教程, 第八部分:Adding Functionality to a Repository (未翻译)
阅读量:4951 次
发布时间:2019-06-12

本文共 15799 字,大约阅读时间需要 52 分钟。

The previous part of my tutorial described . The example application of this blog entry has the same functional requirements, but it will use Querydsl instead of JPA criteria API. This blog entry assumes that you have got experience from both Querydsl And Spring Data JPA. If this is not the case, please read my.

If you have read the previous parts of my , you might have noticed that the described solutions introduced a dependency between the service layer and the Spring Data JPA. This blog entry describes how you can eliminate that dependency (or at least most of it) by adding custom functionality to a Spring Data repository.

Adding Functionality to a Spring Data Repository

You can add  by following these steps:

  • Create an interface for the custom functionality.
  • Implement the created interface.
  • Create the Spring Data JPA repository.

Enough with the theory. It is time to take a look, how I added custom functionality to the person repository of my example application.

Implementing The Example Application

This Section describes how I added the custom functionality to the person repository of my example application. The steps described previously are discussed with more details in following Subsections.

Creating an interface for the custom functionality.

In order to eliminate the dependency between the service layer and the Spring Data JPA, I created an interface called PaginatingPersonRepository. My goal was to move all pagination and sorting logic behind this interface. The source code of my custom interface is given in following:

public
 
interface
 PaginatingPersonRepository
 
{


   
 
/**
     * Finds all persons stored in the database.
     * @return
     */

   
 
public
 List
<Person
>
 findAllPersons
(
)
;


   
 
/**
     * Finds the count of persons stored in the database.
     * @param searchTerm
     * @return
     */

   
 
public
 
long
 findPersonCount
(
String
 searchTerm
)
;


   
 
/**
     * Finds persons for the requested page whose last name starts with the given search term.
     * @param searchTerm    The used search term.
     * @param page  The number of the requested page.
     * @return  A list of persons belonging to the requested page.
     */

   
 
public
 List
<Person
>
 findPersonsForPage
(
String
 searchTerm,
 
int
 page
)
;

}

Implementing the Declared Interface

The next step is to create an implementation for the PaginatingPersonRepository interface. This implementation has following responsibilities:

  • Creating a new  instance and delegating the method calls to it.
  • Obtaining the required Querydsl predicate when a search is performed.
  • Creating the  object which is used to specify the sort order of query results.
  • Creating the  object which id used the specify the wanted page and sort order of the search results.

The implementation of the PaginatingPersonRepository interface is using the PersonPredicatesclass for creating the required Querydsl predicates. The source code of this class is given in following:

import
 
com.mysema.query.types.Predicate
;

import
 
net.petrikainulainen.spring.datajpa.model.QPerson
;


public
 
class
 PersonPredicates
 
{


   
 
public
 
static
 Predicate lastNameIsLike
(
final
 
String
 searchTerm
)
 
{

        QPerson person
 
=
 QPerson.
person
;

       
 
return
 person.
lastName.
startsWithIgnoreCase
(searchTerm
)
;

   
 
}

}

The repository architecture of Spring Data JPA is looking for the custom implementation from the package where the repository was found. The name of the class implementing the custom interface must be constructed by adding a special postfix to the name of the custom interface. The default value of this postfix is Impl, but you can change the postfix by adding a repository-impl-postfix attribute to the namespace configuration element of Spring Data JPA. An example of this is given in following:

<!-- Declares the base package for repositories and states that postfix used to identify custom implementations is FooBar. -->

<jpa:repositories base-package="net.petrikainulainen.spring.datajpa.repository" repository-impl-postfix="FooBar"/>

However, I am going to use the default configuration. Thus, the name of my custom repository class must be PaginatingPersonRepositoryImpl.

Note: If you are using Spring Data JPA 1.2.0 (or newer version), the name of the custom repository implementation must follow the following formula: [The name of the actual repository interface][postfix]. Because the example application use the default configuration, the name of the custom repository implementation must be PersonRepositoryImpl.

The source code of the PaginatingPersonRepositoryImpl class is given in the following:

import
 
org.slf4j.Logger
;

import
 
org.slf4j.LoggerFactory
;

import
 
org.springframework.data.domain.Page
;

import
 
org.springframework.data.domain.PageRequest
;

import
 
org.springframework.data.domain.Pageable
;

import
 
org.springframework.data.domain.Sort
;

import
 
org.springframework.data.jpa.repository.support.JpaEntityInformation
;

import
 
org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation
;

import
 
org.springframework.data.jpa.repository.support.QueryDslJpaRepository
;

import
 
org.springframework.stereotype.Repository
;


import
 
javax.annotation.PostConstruct
;

import
 
javax.persistence.EntityManager
;

import
 
javax.persistence.PersistenceContext
;


import
 
static
 net.
petrikainulainen.
spring.
datajpa.
repository.
PersonPredicates.
lastNameIsLike
;


@
Repository

public
 
class
 PaginatingPersonRepositoryImpl
 
implements
 PaginatingPersonRepository
 
{


   
 
private
 
static
 
final
 Logger LOGGER
 
=
 LoggerFactory.
getLogger
(PaginatingPersonRepositoryImpl.
class
)
;


   
 
protected
 
static
 
final
 
int
 NUMBER_OF_PERSONS_PER_PAGE
 
=
 
5
;


    @PersistenceContext

   
 
private
 EntityManager entityManager
;


   
 
private
 QueryDslJpaRepository
<Person, Long
>
 personRepository
;


   
 
public
 PaginatingPersonRepositoryImpl
(
)
 
{


   
 
}


    @Override

   
 
public
 List
<Person
>
 findAllPersons
(
)
 
{

        LOGGER.
debug
(
"Finding all persons"
)
;

       
 

       
 
//Passes the Sort object to the repository

       
 
return
 personRepository.
findAll
(sortByLastNameAsc
(
)
)
;

   
 
}


    @Override

   
 
public
 
long
 findPersonCount
(
String
 searchTerm
)
 
{

        LOGGER.
debug
(
"Finding person count with search term: "
 
+
 searchTerm
)
;


       
 
//Passes the predicate to the repository

       
 
return
 personRepository.
count
(lastNameIsLike
(searchTerm
)
)
;

   
 
}


    @Override

   
 
public
 List
<Person
>
 findPersonsForPage
(
String
 searchTerm,
 
int
 page
)
 
{

        LOGGER.
debug
(
"Finding persons for page "
 
+
 page
 
+
 
" with search term: "
 
+
 searchTerm
)
;


       
 
//Passes the predicate and the page specification to the repository

        Page requestedPage
 
=
  personRepository.
findAll
(lastNameIsLike
(searchTerm
), constructPageSpecification
(page
)
)
;


       
 
return
 requestedPage.
getContent
(
)
;

   
 
}


   
 
/**
     * Returns a new object which specifies the the wanted result page.
     * @param pageIndex The index of the wanted result page
     * @return
     */

   
 
private
 
Pageable
 constructPageSpecification
(
int
 pageIndex
)
 
{

       
 
Pageable
 pageSpecification
 
=
 
new
 PageRequest
(pageIndex, NUMBER_OF_PERSONS_PER_PAGE, sortByLastNameAsc
(
)
)
;

       
 
return
 pageSpecification
;

   
 
}


   
 
/**
     * Returns a Sort object which sorts persons in ascending order by using the last name.
     * @return
     */

   
 
private
 Sort sortByLastNameAsc
(
)
 
{

       
 
return
 
new
 Sort
(Sort.
Direction.
ASC,
 
"lastName"
)
;

   
 
}



   
 
/**
     * An initialization method which is run after the bean has been constructed.
     * This ensures that the entity manager is injected before we try to use it.
     */

    @PostConstruct

   
 
public
 
void
 init
(
)
 
{

        JpaEntityInformation
<Person, Long
>
 personEntityInfo
 
=
 
new
 JpaMetamodelEntityInformation
<Person, Long
>
(Person.
class, entityManager.
getMetamodel
(
)
)
;

        personRepository
 
=
 
new
 QueryDslJpaRepository
<Person, Long
>
(personEntityInfo, entityManager
)
;

   
 
}


   
 
/**
     * This setter method should be used only by unit tests
     * @param personRepository
     */

   
 
protected
 
void
 setPersonRepository
(QueryDslJpaRepository
<Person, Long
>
 personRepository
)
 
{

       
 
this.
personRepository
 
=
 personRepository
;

   
 
}

}

We also have to verify that the created repository implementation is working as expected. This means that unit tests must be written. The source code of the unit tests is given in following:

import
 
com.mysema.query.types.Predicate
;

import
 
org.junit.Before
;

import
 
org.junit.Test
;

import
 
org.mockito.ArgumentCaptor
;

import
 
org.springframework.data.domain.Page
;

import
 
org.springframework.data.domain.PageImpl
;

import
 
org.springframework.data.domain.Pageable
;

import
 
org.springframework.data.domain.Sort
;

import
 
org.springframework.data.jpa.repository.support.QueryDslJpaRepository
;


import
 
static
 junit.
framework.
Assert.
assertEquals
;

import
 
static
 org.
mockito.
Matchers.
any
;

import
 
static
 org.
mockito.
Mockito.
*;


public
 
class
 PaginatingPersonRepositoryImplTest
 
{


   
 
private
 
static
 
final
 
int
 PAGE_INDEX
 
=
 
2
;

   
 
private
 
static
 
final
 
long
 PERSON_COUNT
 
=
 
5
;

   
 
private
 
static
 
final
 
String
 PROPERTY_LASTNAME
 
=
 
"lastName"
;

   
 
private
 
static
 
final
 
String
 SEARCH_TERM
 
=
 
"searchTerm"
;


   
 
private
 PaginatingPersonRepositoryImpl repository
;


   
 
private
 QueryDslJpaRepository personRepositoryMock
;


    @Before

   
 
public
 
void
 setUp
(
)
 
{

        repository
 
=
 
new
 PaginatingPersonRepositoryImpl
(
)
;


        personRepositoryMock
 
=
 mock
(QueryDslJpaRepository.
class
)
;

        repository.
setPersonRepository
(personRepositoryMock
)
;

   
 
}


    @Test

   
 
public
 
void
 findAllPersons
(
)
 
{

        repository.
findAllPersons
(
)
;


        ArgumentCaptor
<Sort
>
 sortArgument
 
=
 ArgumentCaptor.
forClass
(Sort.
class
)
;

        verify
(personRepositoryMock, times
(
1
)
).
findAll
(sortArgument.
capture
(
)
)
;


        Sort sort
 
=
 sortArgument.
getValue
(
)
;

        assertEquals
(Sort.
Direction.
ASC, sort.
getOrderFor
(PROPERTY_LASTNAME
).
getDirection
(
)
)
;

   
 
}


    @Test

   
 
public
 
void
 findPersonCount
(
)
 
{

        when
(personRepositoryMock.
count
(any
(Predicate.
class
)
)
).
thenReturn
(PERSON_COUNT
)
;


       
 
long
 actual
 
=
 repository.
findPersonCount
(SEARCH_TERM
)
;


        verify
(personRepositoryMock, times
(
1
)
).
count
(any
(Predicate.
class
)
)
;

        verifyNoMoreInteractions
(personRepositoryMock
)
;


        assertEquals
(PERSON_COUNT, actual
)
;

   
 
}


    @Test

   
 
public
 
void
 findPersonsForPage
(
)
 
{

        List
<Person
>
 expected
 
=
 
new
 ArrayList
<Person
>
(
)
;

        Page foundPage
 
=
 
new
 PageImpl
<Person
>
(expected
)
;


        when
(personRepositoryMock.
findAll
(any
(Predicate.
class
), any
(
Pageable.
class
)
)
).
thenReturn
(foundPage
)
;


        List
<Person
>
 actual
 
=
 repository.
findPersonsForPage
(SEARCH_TERM, PAGE_INDEX
)
;


        ArgumentCaptor
<Pageable
>
 pageSpecificationArgument
 
=
 ArgumentCaptor.
forClass
(
Pageable.
class
)
;

        verify
(personRepositoryMock, times
(
1
)
).
findAll
(any
(Predicate.
class
), pageSpecificationArgument.
capture
(
)
)
;

        verifyNoMoreInteractions
(personRepositoryMock
)
;


       
 
Pageable
 pageSpecification
 
=
 pageSpecificationArgument.
getValue
(
)
;


        assertEquals
(PAGE_INDEX, pageSpecification.
getPageNumber
(
)
)
;

        assertEquals
(PaginatingPersonRepositoryImpl.
NUMBER_OF_PERSONS_PER_PAGE, pageSpecification.
getPageSize
(
)
)
;

        assertEquals
(Sort.
Direction.
ASC, pageSpecification.
getSort
(
).
getOrderFor
(PROPERTY_LASTNAME
).
getDirection
(
)
)
;


        assertEquals
(expected, actual
)
;

   
 
}

}

Creating the Spring Data JPA repository

Now it is time to make the custom functionality available to the users of the repository. This is done by making the repository interface extend the created custom interface. As you might remember, the repository interface of my example application is called PersonRepository. Its source code is given in following:

import
 
org.springframework.data.jpa.repository.JpaRepository
;

import
 
org.springframework.data.querydsl.QueryDslPredicateExecutor
;


public
 
interface
 PersonRepository
 
extends
 JpaRepository
<Person, Long
>, PaginatingPersonRepository, QueryDslPredicateExecutor
<Person
>
 
{


}

Using the Custom Functionality

Now when the logic related to pagination and sorting has been moved to the custom repository implementation, the service layer will basically just delegate the method calls forward to the repository and act as a transaction boundary. The PersonService interface has stayed intact, but there has been some changes to the following methods of the RepositoryPersonService class:

  • public long count(String searchTerm)
  • public List<Person> findAll()
  • public List<Person> search(String searchTerm, int pageIndex)

All these methods are simply delegating the method call forward to the repository. The source code of the relevant parts of the RepositoryPersonService class is given in following:

import
 
org.slf4j.Logger
;

import
 
org.slf4j.LoggerFactory
;

import
 
org.springframework.stereotype.Service
;

import
 
org.springframework.transaction.annotation.Transactional
;


import
 
javax.annotation.Resource
;


@Service

public
 
class
 RepositoryPersonService
 
implements
 PersonService
 
{

   
 

   
 
private
 
static
 
final
 Logger LOGGER
 
=
 LoggerFactory.
getLogger
(RepositoryPersonService.
class
)
;


   
 
protected
 
static
 
final
 
int
 NUMBER_OF_PERSONS_PER_PAGE
 
=
 
5
;


    @Resource

   
 
private
 PersonRepository personRepository
;


    @Transactional

    @Override

   
 
public
 
long
 count
(
String
 searchTerm
)
 
{

        LOGGER.
debug
(
"Getting person count for search term: "
 
+
 searchTerm
)
;

       
 
return
 personRepository.
findPersonCount
(searchTerm
)
;

   
 
}


    @Transactional
(readOnly
 
=
 
true
)

    @Override

   
 
public
 List
<Person
>
 findAll
(
)
 
{

        LOGGER.
debug
(
"Finding all persons"
)
;

       
 
return
 personRepository.
findAllPersons
(
)
;

   
 
}


    @Transactional
(readOnly
 
=
 
true
)

    @Override

   
 
public
 List
<Person
>
 search
(
String
 searchTerm,
 
int
 pageIndex
)
 
{

        LOGGER.
debug
(
"Searching persons with search term: "
 
+
 searchTerm
)
;

       
 
return
 personRepository.
findPersonsForPage
(searchTerm, pageIndex
)
;

   
 
}

}

Changes made to the RepositoryPersonService class means that its unit tests must be changed as well. The source code of the changed unit tests is given in following:

import
 
org.junit.Before
;

import
 
org.junit.Test
;

import
 
org.mockito.ArgumentCaptor
;


import
 
static
 junit.
framework.
Assert.
assertEquals
;

import
 
static
 org.
mockito.
Mockito.
*;


public
 
class
 RepositoryPersonServiceTest
 
{


   
 
private
 
static
 
final
 
int
 PAGE_INDEX
 
=
 
1
;

   
 
private
 
static
 
final
 
String
 SEARCH_TERM
 
=
 
"foo"
;

   
 

   
 
private
 RepositoryPersonService personService
;


   
 
private
 PersonRepository personRepositoryMock
;


    @Before

   
 
public
 
void
 setUp
(
)
 
{

        personService
 
=
 
new
 RepositoryPersonService
(
)
;


        personRepositoryMock
 
=
 mock
(PersonRepository.
class
)
;

        personService.
setPersonRepository
(personRepositoryMock
)
;

   
 
}


    @Test

   
 
public
 
void
 count
(
)
 
{

        personService.
count
(SEARCH_TERM
)
;


        verify
(personRepositoryMock, times
(
1
)
).
findPersonCount
(SEARCH_TERM
)
;

        verifyNoMoreInteractions
(personRepositoryMock
)
;

   
 
}


    @Test

   
 
public
 
void
 findAll
(
)
 
{

        List
<Person
>
 persons
 
=
 
new
 ArrayList
<Person
>
(
)
;

        when
(personRepositoryMock.
findAllPersons
(
)
).
thenReturn
(persons
)
;


        List
<Person
>
 returned
 
=
 personService.
findAll
(
)
;


        verify
(personRepositoryMock, times
(
1
)
).
findAllPersons
(
)
;

        verifyNoMoreInteractions
(personRepositoryMock
)
;


        assertEquals
(persons, returned
)
;

   
 
}


    @Test

   
 
public
 
void
 search
(
)
 
{

        personService.
search
(SEARCH_TERM, PAGE_INDEX
)
;


        verify
(personRepositoryMock, times
(
1
)
).
findPersonsForPage
(SEARCH_TERM, PAGE_INDEX
)
;

        verifyNoMoreInteractions
(personRepositoryMock
)
;

   
 
}

}

What is Next?

I have now described to you how you can add custom functionality to a Spring Data repository. Even though this can be a handy in some situations, I think that the situation described in this blog entry is not one of them. To be honest, the benefits obtained from the proposed solution are purely theoretical, and I cannot recommend using this approach for eliminating the dependency between the service layer and Spring Data JPA. You simply don’t get enough bang for your buck.

Sometimes adding functionality to a single repository is not enough. You can also . The implementation of this is left as an exercise for the reader.

The last part of my Spring Data JPA tutorial will summarize what we have learned and give some suggestions about the usage of Spring Data JPA.

PS. If you want to a take a closer look of my example application, you can .

转载于:https://www.cnblogs.com/chenying99/p/3143901.html

你可能感兴趣的文章
第三百五十七天 how can I 坚持
查看>>
【动态规划】流水作业调度问题与Johnson法则
查看>>
startActivityForResult不起作用
查看>>
Python&Selenium&Unittest&BeautifuReport 自动化测试并生成HTML自动化测试报告
查看>>
活现被翻转生命
查看>>
POJ 1228
查看>>
SwaggerUI+SpringMVC——构建RestFul API的可视化界面
查看>>
springmvc怎么在启动时自己执行一个线程
查看>>
流操作的规律
查看>>
Python基础学习15--异常的分类与处理
查看>>
javascript运算符的优先级
查看>>
React + Redux 入门(一):抛开 React 学 Redux
查看>>
13位时间戳和时间格式化转换,工具类
查看>>
vue router-link子级返回父级页面
查看>>
C# 通知机制 IObserver<T> 和 IObservable<T>
查看>>
Code of Conduct by jsFoundation
查看>>
div 只显示两行超出部分隐藏
查看>>
C#小练习ⅲ
查看>>
debounce、throttle、requestAnimationFrame
查看>>
linux下的C语言快速学习—进程和文件
查看>>