在本人的Spring Data JPA教程的第二部分描述瞭如何用Spring Data JPA建立一個簡單的CRUD應用,本博文將描述如何在Spring Data JPA中使用query方法建立自定義查詢,爲了有一個合理的示例,我爲個人應用建立了三個要求:html
如今開始擴展現例應用.java
完成要求的步驟以下:git
Spring Data JPA提供了三種query方法的不一樣方式來建立自定義查詢,每一種方式描述以下.github
Spring Data JPA有一個內置的查詢建立機制,可用於直接從查詢方法的方法名解析查詢。這種機制首先從查詢方法移除共同的前綴,而且從方法名稱的餘下部分解析查詢的約束。查詢生成器機制更多的細節Defining Query Methods Subsection of Spring Data JPA reference documentation。web
使用這種方式是至關簡單的。你所作的就是確保你的repository接口的方法名稱的建立是與entity對象的屬性名稱與支持的關鍵詞相結合的。Query Creation Subsection of the Spring Data JPA reference documentation 有很好的關於支持的關鍵詞的用法的例子。spring
用這種方式的repository方法的源碼以下:apache
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支持,你就倒黴了。
一個很好的例子是如此情形:此刻你沒法在你的方法的名稱使用小寫的關鍵詞。這意味着,這種方式不能用於完成我在開始指定的要求。app
Spring Data JPA還提供JPA命名查詢的支持。你有如下聲明命名查詢方案:ide
使用所建立的命名查詢你所要作的惟一的事情是,你的資料庫界面,以配合您的命名查詢的名稱命名的查詢方法。我選擇在個人實體類中使用@ NamedQuery註釋指定命名查詢。post
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註解被用於經過使用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
本文版權歸做者全部,未經做者贊成,嚴禁轉載及用做商業傳播,不然將追究法律責任。