这一节描述了使用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但,可是不能把它应用到一个触发计算页面总数的数量查询上面。