在本人的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
本文版权归做者全部,未经做者赞成,严禁转载及用做商业传播,不然将追究法律责任。