這一節描述了使用Spring Data JPA建立查詢的各類方式。html
一般狀況下,查詢的建立算法是根據4.2文檔的說明來工做的,下面的例子展現了查詢方法會轉換成什麼:sql
例子5二、從方法名建立查詢數據庫
public interface UserRepository extends Repository<User, Long> { List<User> findByEmailAddressAndLastname(String emailAddress, String lastname); }
咱們使用JPA的標準API建立了一個查詢,可是,基本上,這個查詢會轉換成這樣的sql語句:select u from User u where u.emailAddress = ?1 and u.lastname = ?2
。Spring Data JPA不會遍歷檢查屬性,詳情請看4.4.3節。api
下面的列表展現了JPA支持的關鍵字和方法會轉換成的內容:
表格三、方法名裏面支持的關鍵字數組
關鍵字 | 示例 | JPQL片斷 |
---|---|---|
And | findByLastnameAndFirstname | ... where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | ... where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs,findByFirstnameEquals | ... where x.firstname = ?1 |
Between | findByStartDateBetween | ... where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | ... where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | ... where x.age <= ?1 |
GreaterThan | findByAgeGreaterThan | ... where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | ... where x.age >= ?1 |
After | findByStartDateAfter | ... where x.startDate > ?1 |
Before | findByStartDateBefore | ... where x.startDate < ?1 |
IsNull | findByAgeIsNull | ... where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | ... where x.age not null |
Like | findByFirstnameLike | ... where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | ... where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | ... where x.firstname like ?1 (參數會綁定到%後面) |
EndingWith | findByFirstnameEndingWith | ... where x.firstname like ?1 (參數會綁定在%前面) |
Containing | findByFirstnameContaining | ... where x.firstname like ?1 (參數會綁定在兩個%中間) |
OrderBy | findByAgeOrderByLastnameDesc | ... where x.age = ?1 order by lastname desc |
Not | findByLastnameNot | ... where x.lastname <> ?1 |
In | findByAgeIn(Collection<Age> ages) | ... where x.age in ?1 |
NotIn | findByAgeNotIn(Connection<Age> ages) | ... where x.age not in ?1 |
True | findByActiveTrue() | ... where x.active = true |
Flase | findByActiveFalse() | ... where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | ... where UPPER(x.firstname) = UPPER(?1) |
In和NotIn容許有一個Collection任何子類來作爲參數,能夠是數組或可變變量。對於相同邏輯的其餘的語法,可查看附錄C。安全
例子使用了<named-query />元素和@NamedQuery註釋。這些查詢配置元素在JPA查詢語言裏面定義了。固然,你還可使用<named-native-query />和@NamedNativeQuery。這些元素和註釋可讓你使用特定數據庫的本地查詢語句。oracle
XML命名查詢的定義
略。。。app
基於註釋的配置
基於註釋的配置能夠不用去配置其餘的配置文件,這是一個優點,能夠下降維護成本。可是沒聲明一個新的查詢,都須要從新編譯domain類。
例子5四、基於註釋的查詢配置
@Entity @NamedQuery(name = "User.findByEmailAddress", query = "select u from User u where u.emailAddress = ?1") public class User { }
聲明接口
要讓命名查詢能夠執行,須要給UserRepository指定以下接口:
例子5五、在UserRepository裏聲明查詢方法
public interface UserRepository extends JpaRepository<User, Long> { List<User> findByLastname(String lastname); User findByEmailAddress(String emailAddress); }
Spring Data JPA在調用這個方法的時候,會嘗試解析方法名,從domain類的簡單名開始,接着是名字後面的.
點分隔符。所以上面的例子會嘗試從方法名裏來建立一個命名查詢。
使用命名查詢來進行查詢entities是一個有效的方法,而且對少許的查詢都是能工做的很好。對於查詢他們是綁定到了java方法上面執行的。你能夠直接使用Spring Data JPA的@Query註釋來直接綁定它們,而不是將它們註釋到domain類。這個domain類是從特定的持久化信息裏面釋放的,並將查詢定位到repository接口。 使用@NamedQuery經過查詢定義來註釋一個上面的查詢方法,或者在orm.xml裏面定義一個查詢。
下面的例子展現了一個使用@Query註釋建立的查詢
例子5六、在查詢方法上面使用@Query來聲明一個查詢
public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.emailAddress = ?1") User findByEmailAddress(String emailAddress); }
查詢執行算法在使用@Query手動建立查詢的時候容許在查詢裏面使用高級的LIKE進行定義,以下例子所示:
例子5七、@Query裏面的like表達式
public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.firstname like %?1") List<User> findByFirstnameEndsWith(String firstname); }
上面的例子裏,like分隔符(%)會被分辨出來的,而且查詢會轉換成有效的JPQL查詢(刪除了%)。上面方法的執行,傳遞給方法的參數會匹配like模式。
@Query註釋能夠經過將nativeQuery設置爲true,來進行本地查詢語句的執行,以下例子所示:
public interface UserRepository extends JpaRepository<User, Long> { @Query(value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true) User findByEmailAddress(String emailAddress); }
Spring Data JPA目前不支持本地查詢裏面的動態排序,由於它必須處理實際聲明的查詢,對於本地的SQL是不可靠的。可是你能夠經過在本地查詢裏面指定查詢的數量來實現頁的查詢,以下例子所示:
例子5九、經過在查詢方法上面使用@Query註釋聲明一個本地的頁碼總數的查詢
public interface UserRepository extends JpaRepository<User, Long> { @Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1", countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1", nativeQuery = true) Page<User> findByLastname(String lastname, Pageable pageable); }
一個相似的方法是使用命名本地查詢,經過添加.count
到你的查詢後面。你可能須要給你的count query註冊一個map結果。
排序能夠經過提供一個PageRequest或直接用Sort。Sort實例中實際使用的屬性須要跟domain模型匹配,這意味着他們須要解析成一個屬性或者用於query裏面的別名。JPQL將這定義成一個狀態字段路徑表達式。
使用任何不能引用的路徑表達式會拋出異常。
然而,和@Query一塊兒使用Sort,可讓你潛入進包含方法到Order By字句的非檢查的路徑Order的實例。這是可能的,由於Orderis追加到查詢字符串後面的。默認狀況下,Spring Data JPA拒絕包含方法調用的任何Order的實例,可是你可使用JpaSort.unsafe來添加潛在不安全的排序。
下面的例子使用了Sort和JpaSort,JpaSort上面包含了一個不安全的選項:
例子60、使用Sort和JpaSort
public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.lastname like ?1%") List<User> findByAndSort(String lastname, Sort sort); @Query("select u.id, LENGTH(u.firstname) as fn_len from User u where u.lastname like ?1%") List<Object[]> findByAsArrayAndSort(String lastname, Sort sort); } repo.findByAndSort("lannister", new Sort("firstname")); (1) repo.findByAndSort("stark", new Sort("LENGTH(firstname)")); (2) repo.findByAndSort("targaryen", JpaSort.unsafe("LENGTH(firstname)")); (3) repo.findByAsArrayAndSort("bolton", new Sort("fn_len")); (4)
(1)有效的Sort,表達式指向domain模型的屬性
(2)無效的包含方法調用的Sort,拋出異常
(3)有效的Sort,明確的包含不安全的order
(4)有效的Sort,表達式指向方法別名
如前面的例子所講,默認狀況下,Spring Data JPA使用基於位置的參數綁定。當重構有關參數位置時,會讓查詢方法容易出錯。要解決這個問題,可使用@Param註釋個體方法的參數一個具體的名字,而且綁定名字到查詢上面,以下例子所示:
例子6一、使用命名參數
public interface UserRepository extends JpaRepository<User, Long> { @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname") User findByLastnameOrFirstname(@Param("lastname") String lastname, @Param("firstname") String firstname); }
方法參數在定義的查詢裏面,位置已經交換了
對於版本4,Spring徹底支持java8的使用編譯器的--parameters參數來發現參數名。在構建中使用這個參數能夠做爲調試信息的替代,你能夠在命名參數上面省略@Param註釋。 ###5.3.七、使用SpEL表達式 對於Sprng Data JPA的1.4版本,咱們支持使用@Query手動定義的查詢裏面對受限制的SpEl模板表達式的使用。上面查詢的執行,這些表達式的評價依據是變量的集合。Spring Data JPA支持名爲entityName的變量。它的用法是
select x from #{#entityName} x
。它會插入給定的repository的doamin類型相關的entityName。entityName按以下方式解析:若是的domain的屬性上面設置了@Entity註釋,則使用它。不然,使用domain類型的簡單的類名。
下面的例子演示了一個查詢字符串裏#{#entityName}的用例,使用查詢方法和手動定義查詢,來定義一個repository接口:
例子6二、在repository查詢方法裏使用SpEL表達式-entityName
@Entity public class User { @Id @GeneratedValue Long id; String lastname; } public interface UserRepository extends JpaRepository<User,Long> { @Query("select u from #{#entityName} u where u.lastname = ?1") List<User> findByLastname(String lastname); }
爲了不陳述使用了@Query註釋的查詢字符串裏面的真實的entity名稱,你可使用#{#entityName}變量。
固然你能夠在查詢聲明裏面直接使用User
,可是它須要你來修改查詢。對#entityName的引用能夠在之後將潛在的User類映射到不一樣的entity名稱上面(如:@Entity(name = "MyUser"))。
查詢字符串裏面的#{#entityName}的其餘用法是,若是你想給指定的domain類型建立指定的repository接口的普通的repository,不要重複在指定接口上面定義自定的方法,你能夠在普通的repository接口上面裏面經過在查詢字符串上面使用@Query註釋來使用entityName表達式,以下例子所示:
例子6三、在repository的查詢方法裏面使用SpEL表達式-entityName的繼承
@MappedSuperclass public abstract class AbstractMappedType { … String attribute } @Entity public class ConcreteType extends AbstractMappedType { … } @NoRepositoryBean public interface MappedTypeRepository<T extends AbstractMappedType> extends Repository<T, Long> { @Query("select t from #{#entityName} t where t.attribute = ?1") List<T> findAllByAttribute(String attribute); } public interface ConcreteRepository extends MappedTypeRepository<ConcreteType> { … }
上面的例子裏,MappendTypeRepository
是少許的幾個繼承了AbStractMappendType
的通用的父接口。它還定義了findAllByAttribute(...)
方法,它能夠被用到指定repository接口的實例上面,若是你如今在ConcreteRepository
上面執行findAllByAttribute(...)
方法,則執行下面的查詢:select t from ConcreteType t where t.attribute = ?1
。
SpEL表達式去操做參數還能夠被用來操做方法的參數。在這個SpEL表達式裏entity名是不可用的,可是參數是可用的,能夠經過名稱或索引來訪問,以下例子所示: 例子6四、在repository查詢裏面使用SpEL表達式-訪問參數
@Query("select u from User u where u.firstname = ?1 and u.firstname=?#{[0]} and u.emailAddress = ?#{principal.emailAddress}") List<User> findByFirstnameAndCurrentUserWithCustomQuery(String firstname);
若是想提供LIKE
條件,能夠在值的前面或後面加上&
。能夠在參數或SpEL表達式的前面或後面添加%
。以下例子所示: *例子6五、在repository查詢裏面使用SpEL表達式-通配符的縮寫
@Query("select u from User u where u.lastname like %:#{[0]}% and u.lastname like %:lastname%") List<User> findByLastnameWithSpelExpression(@Param("lastname") String lastname);
當使用一個帶值的LIKE
條件的時候,是從一個不安全的源碼裏面獲得的,所以須要去除隱患,它們不能包含任何的通配符以及任何能夠攻擊數據庫的字符,爲了解決這個問題,可使用SpEL表達式裏面的escape(String)方法,它會在第一個單字符的參數和第二個參數開始給前面加上_
和%
前綴結合LIKE
表達式的escape方法能夠用在JPQL和標準的SQL,能夠很方便的處理參數裏面的特殊字符。 例子6六、在repository查詢裏面使用SpEL表達式-處理輸入值的攻擊隱患
@Query("select u from User u where u.firstname like %?#{escape([0])}% escape ?#{escapeCharacter()}") List<User> findContainingEscaped(String namePart);
在repository接口裏聲明一個方法findContainingEscaped("Peter_")
,能夠查找到Peter_Paker
,可是查找不到Peter Paker
,escape字符能夠經過使用@EnableJpaRepositories
註釋來設置escapeCharacter
。注意escape(String)方法用在SpEL上下文裏,只能做用在JPQL和SQL標註的通配符_
和%
上面。若是底層的數據庫或JPA的實現支付額外的通配符,這些通配符都不會被escape處理。
所有上一節的說明,是如何來聲明一個查詢來訪問entity或entity的集合。你可使用4.6節提到的內容來修改查詢的行爲。全部的這些方法對於所有的自定義方法都是可行的,你能夠在在查詢上面使用@Modify註釋只修改參數,以下例子所示: 例子6七、定義操做查詢
@Modifying @Query("update User u set u.firstname = ?1 where u.lastname = ?2") int setFixedFirstnameFor(String firstname, String lastname);
調用這個方法會更新指定的值。對於一個EntityManager
在執行完修改查詢後可能會包含未修改的entities,咱們不會自動去清理(詳情請看EntityManager.clear()文檔),由於這會有效的刪除EntityManager
裏面等待的未刷新的修改,若是你但願EntityManager
自動清理,能夠設置@Modifying
註釋的屬性clearAutomatically
爲true
。 @Modifying
註釋只有結合@Query
註釋使用纔會生效。派生的或自定義的查詢不須要這個註釋。
Spring Data JPA還支持派生刪除查詢,可讓你避免明確的聲明一個JPQL查詢,以下例子所示: 例子6八、使用派生的刪除查詢
interface UserRepository extends Repository<User, Long> { void deleteByRoleId(long roleId); @Modifying @Query("delete from User u where user.role.id = ?1") void deleteInBulkByRoleId(long roleId); }
儘管deleteByRoleId(...)
方法看起來向deleteInBulkRoleId(...)
方法,這兩個方法在執行方式的聲明上有不一樣點。顧名思義,後面的方法針對數據庫發出一個JPQL查詢(定義在註釋裏的查詢)。這意味着即便當前已經加載的User
也看不到執行後的聲明週期的回調。
要保證生命週期會真正的執行,deleteByRoleId(...)
調用會執行一個查詢,而且一個一個的刪除返回的實例,所以它的持久化提供者能夠在entities上面真正的執行@PreRemove
回調。
事實上,派生的刪除查詢是一個快捷方式來執行查詢而且在結果上調用CrudRepository.delete(Iterale<User>)
而且保持行爲和CrudRepository裏面實現的其餘delete(...)
方法一直。
應用JPA查詢提示到你repository接口的查詢聲明裏,你可使用@QueryHints
註釋。當處理分頁時,它帶着一個JPA的@QueryHint
註釋數組加上默認爲false的boolean標誌應用到額外的總數查詢的觸發,以下例子所示:
*例子6九、在repository方法上面使用QueryHints
public interface UserRepository extends Repository<User, Long> { @QueryHints(value = { @QueryHint(name = "name", value = "value")}, forCounting = false) Page<User> findByLastname(String lastname, Pageable pageable); }
上面的聲明會給實際的查詢應用一個配置@QueryHints但,可是不能把它應用到一個觸發計算頁面總數的數量查詢上面。