Spring Data JPA教程, 第三部分: Custom Queries with Query Methods(翻譯)


在本人的Spring Data JPA教程的第二部分描述了如何用Spring Data JPA創建一個簡單的CRUD應用,本博文將描述如何在Spring Data JPA中使用query方法創建自定義查詢,為了有一個合理的示例,我為我的應用創建了三個要求:

  • 實現通過他們的last name作為搜索條件搜索到person.
  • 搜索功能必須返回這樣的person,它們的last name准確匹配搜索條件。
  • 搜索不區分大小寫.

現在開始擴展示例應用.

所需步驟

完成要求的步驟如下:

  • 創建一個query方法.
  • 使用該創建的query方法.

Spring Data JPA提供了三種query方法的不同方式來創建自定義查詢,每一種方式描述如下.

通過解析方法名創建查詢

Spring Data JPA有一個內置的查詢創建機制,可用於直接從查詢方法的方法名解析查詢。這種機制首先從查詢方法移除共同的前綴,並且從方法名稱的余下部分解析查詢的約束。查詢生成器機制更多的細節Defining Query Methods Subsection of Spring Data JPA reference documentation

使用這種方式是相當簡單的。你所做的就是確保你的repository接口的方法名稱的創建是與entity對象的屬性名稱與支持的關鍵詞相結合的。Query Creation Subsection of the Spring Data JPA reference documentation 有很好的關於支持的關鍵詞的用法的例子。

用這種方式的repository方法的源碼如下:

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

/**
 * Specifies methods used to obtain and modify person related information
 * which is stored in the database.
 * @author Petri Kainulainen
 */
public interface PersonRepository extends JpaRepository<Person, Long> {

    /**
     * Finds persons by using the last name as a search criteria.
     * @param lastName  
     * @return  A list of persons which last name is an exact match with the given last name.
     *          If no persons is found, this method returns an empty list.
     */
    public List<Person> findByLastName(String lastName);
}

 這種方式的優勢是,它是相當快速的實現簡單的查詢。另一方面,如果你的查詢有很多參數,方法名稱將是相當冗長、丑陋。另外,如果你需要的關鍵詞不被Spring Data JPA支持,你就倒霉了。

一個很好的例子是如此情形:此刻你無法在你的方法的名稱使用小寫的關鍵詞。這意味着,這種方式不能用於完成我在開始指定的要求。

JPA 命名查詢

Spring Data JPA還提供JPA命名查詢的支持你有以下聲明命名查詢方案:

  • 您可以以JPA查詢語言使用 named-queryXML元素或@ NamedQuery注釋來創建命名查詢。
  • 如果你准備將你的應用與具體的數據平台綁定,您可以以SQL方式使用named-native-queryXML元素或@NamedNative查詢創建來創建查詢。

使用所創建的命名查詢你所要做的唯一的事情是,你的資料庫界面,以配合您的命名查詢的名稱命名的查詢方法。我選擇在我的實體類中使用@ NamedQuery注釋指定命名查詢

Person類的源碼如下:

import org.apache.commons.lang.builder.ToStringBuilder;

import javax.persistence.*;

/**
 * An entity class which contains the information of a single person.
 * @author Petri Kainulainen
 */
@Entity
@NamedQuery(name = "Person.findByName", query = "SELECT p FROM Person p WHERE LOWER(p.lastName) = LOWER(?1)")
@Table(name = "persons")
public class Person {
    
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    @Column(name = "creation_time", nullable = false)
    private Date creationTime;
    
    @Column(name = "first_name", nullable = false)
    private String firstName;

    @Column(name = "last_name", nullable = false)
    private String lastName;
    
    @Column(name = "modification_time", nullable = false)
    private Date modificationTime;
    
    @Version
    private long version = 0;

    public Long getId() {
        return id;
    }

    /**
     * Gets a builder which is used to create Person objects.
     * @param firstName The first name of the created user.
     * @param lastName  The last name of the created user.
     * @return  A new Builder instance.
     */
    public static Builder getBuilder(String firstName, String lastName) {
        return new Builder(firstName, lastName);
    }
    
    public Date getCreationTime() {
        return creationTime;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    /**
     * Gets the full name of the person.
     * @return  The full name of the person.
     */
    @Transient
    public String getName() {
        StringBuilder name = new StringBuilder();
        
        name.append(firstName);
        name.append(" ");
        name.append(lastName);
        
        return name.toString();
    }

    public Date getModificationTime() {
        return modificationTime;
    }

    public long getVersion() {
        return version;
    }

    public void update(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
    
    @PreUpdate
    public void preUpdate() {
        modificationTime = new Date();
    }
    
    @PrePersist
    public void prePersist() {
        Date now = new Date();
        creationTime = now;
        modificationTime = now;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }

    /**
     * A Builder class used to create new Person objects.
     */
    public static class Builder {
        Person built;

        /**
         * Creates a new Builder instance.
         * @param firstName The first name of the created Person object.
         * @param lastName  The last name of the created Person object.
         */
        Builder(String firstName, String lastName) {
            built = new Person();
            built.firstName = firstName;
            built.lastName = lastName;
        }

        /**
         * Builds the new Person object.
         * @return  The created Person object.
         */
        public Person build() {
            return built;
        }
    }

    /**
     * This setter method should only be used by unit tests.
     * @param id
     */
    protected void setId(Long id) {
        this.id = id;
    }
}

我的PersonRepository接口的相關部分如下:

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

/**
 * Specifies methods used to obtain and modify person related information
 * which is stored in the database.
 * @author Petri Kainulainen
 */
public interface PersonRepository extends JpaRepository<Person, Long> {

    /**
     * Finds person by using the last name as a search criteria.
     * @param lastName
     * @return  A list of persons whose last name is an exact match with the given last name.
     *          If no persons is found, this method returns null.
     */
    public List<Person> findByName(String lastName);
}

如果你的應用規模小或者你不得不使用原生態的查詢,使用命名查詢是有效地選擇,如果你的應用有大量的自定義查詢,這種方式將你的entity類的代碼充斥垃圾查詢聲明,你當然可以使用XML配置來避免這種情況,不過在本人看來這種方式更恐怖 

Using named queries is valid option if your application is small or if you have to use native queries. If your application has a lot of custom queries, this approach will litter the code of your entity class with query declarations (You can of course use the XML configuration to avoid this but in my opinion this approach is even more horrible).

@Query注解

@Query注解被用於通過使用JPA查詢語言創建查詢,並且直接綁定這些查詢到你的repository接口的方法,當查詢方法別調用,Spring Data JPA將執行通過@Query注解指定的查詢(如果@Query 注解與命名查詢有沖突,將執行通過使用@Query 注解指定的查詢)。

通過使用這種方式實現的repository方法的源碼如下:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

/**
 * Specifies methods used to obtain and modify person related information
 * which is stored in the database.
 * @author Petri Kainulainen
 */
public interface PersonRepository extends JpaRepository<Person, Long> {

    /**
     * Finds a person by using the last name as a search criteria.
     * @param lastName
     * @return  A list of persons whose last name is an exact match with the given last name.
     *          If no persons is found, this method returns an empty list.
     */
    @Query("SELECT p FROM Person p WHERE LOWER(p.lastName) = LOWER(:lastName)")
    public List<Person> find(@Param("lastName") String lastName);
}

 這種方式使你可以訪問JPA查詢語言,並且在它們所屬的repository層保持你的查詢。另一方面,如果JPA查詢語言不能不能用於創建你需要的查詢,你不能使用@Query 注解(在本教程的下一部分我將描述更多的高級策略)

使用創建的查詢方法

在Spring Data JPA中,本人已經向你描述三種方式來創建查詢方法,下一步是來看一看用於創建查詢方法的服務類.

SearchType枚舉標示用於查詢的方法,其源碼如下:

/**
 * Describes the search type of the search. Legal values are:
 * <ul>
 *     <li>METHOD_NAME which means that the query is obtained from the method name of the query method.</li>
 *     <li>NAMED_QUERY which means that a named query is used.</li>
 *     <li>QUERY_ANNOTATION which means that the query method annotated with @Query annotation is used.</li>
 * </ul>
 * @author Petri Kainulainen
 */
public enum SearchType {
    METHOD_NAME,
    NAMED_QUERY,
    QUERY_ANNOTATION;
}

SearchDTO是一個簡單的DTO對象,它包含了用戶給出的、標識同於查詢方法的搜索條件,其源碼如下:

import org.apache.commons.lang.builder.ToStringBuilder;

/**
 * A DTO class which is used as a form object in the search form.
 * @author Petri Kainulainen
 */
public class SearchDTO {

    private String searchTerm;

    private SearchType searchType;

    public SearchDTO() {

    }

    public String getSearchTerm() {
        return searchTerm;
    }

    public void setSearchTerm(String searchTerm) {
        this.searchTerm = searchTerm;
    }

    public SearchType getSearchType() {
        return searchType;
    }

    public void setSearchType(SearchType searchType) {
        this.searchType = searchType;
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
}

PersonService接口得到一新方法, PersonService接口相關部分如下:

/**
 * Declares methods used to obtain and modify person information.
 * @author Petri Kainulainen
 */
public interface PersonService {

    /**
     * Searches persons by using the search criteria given as a parameter.
     * @param searchCriteria
     * @return  A list of persons matching with the search criteria. If no persons is found, this method
     *          returns an empty list.
     * @throws IllegalArgumentException if search type is not given.
     */
    public List<Person> search(SearchDTO searchCriteria);
}

 search()方法的具體實現在於選擇正確的查詢方法並傳遞給出的搜索詞,我的search()方法實現源碼如下:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * This implementation of the PersonService interface communicates with
 * the database by using a Spring Data JPA repository.
 * @author Petri Kainulainen
 */
@Service
public class RepositoryPersonService implements PersonService {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(RepositoryPersonService.class);
    
    @Resource
    private PersonRepository personRepository;

    @Transactional(readOnly = true)
    @Override
    public List<Person> search(SearchDTO searchCriteria) {
        LOGGER.debug("Searching persons with search criteria: " + searchCriteria);
        
        String searchTerm = searchCriteria.getSearchTerm();
        SearchType searchType = searchCriteria.getSearchType();
        
        if (searchType == null) {
            throw new IllegalArgumentException();
        }
         
        return findPersonsBySearchType(searchTerm, searchType);
    }
    
    private List<Person> findPersonsBySearchType(String searchTerm, SearchType searchType) {
        List<Person> persons;

        if (searchType == SearchType.METHOD_NAME) {
            LOGGER.debug("Searching persons by using method name query creation.");
            persons = personRepository.findByLastName(searchTerm);
        }
        else if (searchType == SearchType.NAMED_QUERY) {
            LOGGER.debug("Searching persons by using named query");
            persons = personRepository.findByName(searchTerm);
        }
        else {
            LOGGER.debug("Searching persons by using query annotation");
            persons = personRepository.find(searchTerm);
        }

        return persons;
    }
}

 當然,創建的search()必須進行測試。給出了以下相關的單元測試的源代碼:

import org.junit.Before;
import org.junit.Test;

import static junit.framework.Assert.assertEquals;
import static org.mockito.Mockito.*;

public class RepositoryPersonServiceTest {

    private static final String LAST_NAME = "Bar";
    
    private RepositoryPersonService personService;

    private PersonRepository personRepositoryMock;

    @Before
    public void setUp() {
        personService = new RepositoryPersonService();

        personRepositoryMock = mock(PersonRepository.class);
        personService.setPersonRepository(personRepositoryMock);
    }

    @Test
    public void searchWhenSearchTypeIsMethodName() {
        SearchDTO searchCriteria = createSearchDTO(LAST_NAME, SearchType.METHOD_NAME);
        List<Person> expected = new ArrayList<Person>();
        when(personRepositoryMock.findByLastName(searchCriteria.getSearchTerm())).thenReturn(expected);
        
        List<Person> actual = personService.search(searchCriteria);
        
        verify(personRepositoryMock, times(1)).findByLastName(searchCriteria.getSearchTerm());
        verifyNoMoreInteractions(personRepositoryMock);
        
        assertEquals(expected, actual);
    }

    @Test
    public void searchWhenSearchTypeIsNamedQuery() {
        SearchDTO searchCriteria = createSearchDTO(LAST_NAME, SearchType.NAMED_QUERY);
        List<Person> expected = new ArrayList<Person>();
        when(personRepositoryMock.findByName(searchCriteria.getSearchTerm())).thenReturn(expected);

        List<Person> actual = personService.search(searchCriteria);

        verify(personRepositoryMock, times(1)).findByName(searchCriteria.getSearchTerm());
        verifyNoMoreInteractions(personRepositoryMock);

        assertEquals(expected, actual);
    }

    @Test
    public void searchWhenSearchTypeIsQueryAnnotation() {
        SearchDTO searchCriteria = createSearchDTO(LAST_NAME, SearchType.QUERY_ANNOTATION);
        List<Person> expected = new ArrayList<Person>();
        when(personRepositoryMock.find(searchCriteria.getSearchTerm())).thenReturn(expected);

        List<Person> actual = personService.search(searchCriteria);

        verify(personRepositoryMock, times(1)).find(searchCriteria.getSearchTerm());
        verifyNoMoreInteractions(personRepositoryMock);

        assertEquals(expected, actual);
    }

    @Test(expected = IllegalArgumentException.class)
    public void searchWhenSearchTypeIsNull() {
        SearchDTO searchCriteria = createSearchDTO(LAST_NAME, null);

        personService.search(searchCriteria);

        verifyZeroInteractions(personRepositoryMock);
    }
    
    private SearchDTO createSearchDTO(String searchTerm, SearchType searchType) {
        SearchDTO searchCriteria = new SearchDTO();
        searchCriteria.setSearchTerm(searchTerm);
        searchCriteria.setSearchType(searchType);
        return searchCriteria;
    }
}

下一步內容?

本人已經向你描述了在Spring Data JPA中如何使用query方法來創建自定義查詢,如果你對查看我的實踐的示例應用感興趣,你可以從Github獲取,我的Spring Data JPA教程的下一部分描述如何用Spring Data JPA創建JPA條件查詢.

--------------------------------------------------------------------------- 

本系列Spring Data JPA 教程翻譯系本人原創

作者 博客園 刺蝟的溫馴 

本文鏈接http://www.cnblogs.com/chenying99/archive/2013/06/19/3143539.html

本文版權歸作者所有,未經作者同意,嚴禁轉載及用作商業傳播,否則將追究法律責任。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM