简介
如果你已经用 春天数据JPA工作过一段时间,你可能已经熟悉了派生查询方法。
public interface MyRepository extends JpaRepository<Entity, Long> {
List<Entity> findByOrganizationName(String name);
}
它们是一种灵巧而快速的方式,通过简单地定义方法名,就可以减轻Spring Data JPA编写查询的负担。
在这个假想的场景中,我们为一个Entity 类定义了一个JpaRepository ,该类有一个名为organizationName 的属性。而不是在实现MyRepository 的服务中实现这个方法--Spring Data JPA会根据方法的名称自动生成一个查询。它将生成一个查询,返回所有Entity 记录的列表,其中有一个匹配的organizationName 。
一旦该方法被调用,就会发出以下Hibernate请求。
select
client0_.id as id1_0_, client0_.age as age2_0_, client0_.name
as
name3_0_, client0_.organinzation as organinz4_0_
from
client client0_ where client0_.organinzation=?
这是Spring Data JPA的一个极其灵活和强大的功能,它允许你 bootstrap查询,而无需编写查询本身,甚至无需在后端实现任何处理逻辑。
然而,当需要复杂的查询时,它们会变得非常难以创建。
public interface PropertyRepository extends JpaRepository<Property, Long> {
List<Property> findPropertiesByTransactionTypeAndPropertyType(@Param("transaction_type") TransactionType transactionType, @Param("property_type") PropertyType propertyType);
}
而这仅仅是针对_两个_参数。当你想创建一个5个参数的查询时会发生什么?
另外,你要创建多少种方法变化?
这时你很可能想自己编写查询。这可以通过@Query 注解来实现。
@Query注解适用于JpaRepository接口中的方法级别,并与单个方法相关。注解中使用的语言取决于你的后端,或者你可以使用中性的 JPQL(用于关系型数据库)。)
在JpaRepository 变体的情况下,比如MongoRepository ,自然,你会写Mongo查询,而如果你使用的是关系型数据库,你会写SQL查询。
当方法被调用时,@Query 注解中的查询就会启动并返回结果。
**注意:**本指南将涵盖Spring Data JPA与关系型数据库的耦合,并将使用JPQL和本地SQL,这不适用于非关系型数据库。
如果您想了解更多关于编写MongoDB原生查询的信息,请阅读我们的@Query注释与MongoDB指南(即将发布!)。
什么是JPQL?
JPQL代表的是 Java持久性查询语言.它被定义在JPA规范中,是一种_面向对象的查询语言_,用于对持久化实体进行数据库操作。
JPA作为调解人,将_JPQL查询_转换为_SQL查询_来执行。
**注意:**应该注意的是,与本地SQL相比,JPQL不与数据库表、记录和字段交互,而是与Java类和实例交互。
它的一些特点包括。
- 它是一种与平台无关的查询语言。
- 它简单而稳健。
- 它可以与任何类型的关系数据库一起使用。
- 它可以被静态地声明为元数据,也可以动态地建立在代码中。
- 它是不分大小写的。
如果你的数据库可以改变或从开发到生产的变化,只要它们都是关系型的--JPQL就能创造奇迹,你可以写JPQL查询来创建通用逻辑,可以反复使用。
如果你还不熟悉JPQL,请阅读我们的《理解JPQL指南》(即将推出!)。
如果你使用的是非关系型数据库,如MongoDB--你将编写该数据库的原生查询。
同样,如果你想阅读更多关于编写MongoDB原生查询的信息,请阅读我们的@Query Annotation与MongoDB指南(即将推出!)。
JPQL查询结构
JPQL的语法与SQL的语法非常相似。由于大多数开发人员已经熟悉了SQL的语法,所以学习和使用JPQL变得很容易。
JPQL的SELECT,UPDATE 和DELETE 查询的结构是。
SELECT ... FROM ...
[WHERE ...]
[GROUP BY ... [HAVING ...]]
[ORDER BY ...]
DELETE FROM ... [WHERE ...]
UPDATE ... SET ... [WHERE ...]
我们稍后将在@Query 注解中手动编写这些查询。
了解@查询注解
@Query 注解只能用于注释资源库接口方法。对注解方法的调用将触发其中发现的语句的执行,它们的用法是非常直接的。
@Query 注解同时支持本地 SQL 和 JPQL。当使用本地SQL时,注解的nativeQuery 参数应该被设置为true 。
@Query("NATIVE_QUERY...", nativeQuery=true)
List<Entity> findAllByName(String name);
要从数据库中选择所有客户,我们可以使用本地查询或JPQL。
@Query("SELECT(*) FROM CLIENT", nativeQuery=true)
List<Client> findAll();
@Query("SELECT client FROM Client client")
List<Client> findAll();
**就这样了。**你的工作完成了--类似于派生查询方法为你处理工作的方式--当你调用findAll() 方法时,这个@Query 就会启动。
不过,如果你只想找到所有的记录,使用派生查询方法会更容易一些--这就是它们存在的意义。当你想把动态变量作为参数传递给查询本身时,你会想写自己的查询。
引用方法参数
@Query 注释中的本地查询和JPQL查询都可以接受 注释的方法参数,它进一步分类为。
- 基于位置的参数
- 命名的参数
当使用基于位置的参数时,你必须跟踪你提供参数的顺序。
@Query("SELECT c FROM Client c WHERE c.name = ?1 AND c.age = ?2")
List<Client> findAll(String name, int age);
传递给方法的第一个参数被映射到?1 ,第二个被映射到?2 ,等等。如果你不小心调换了这些参数--你的查询可能会抛出一个异常,或者默默地产生错误的结果。
另一方面,被命名的参数是被命名的,无论其位置如何,都可以通过名称来引用。
@Query("SELECT c FROM Client c WHERE c.name = :name and c.age = :age")
List<Client> findByName(@Param("name") String name, @Param("age") int age);
@Param 注解中的名称与@Query 注解中的命名参数相匹配,所以你可以自由地调用你的变量,但为了保持一致性,我们建议使用相同的名称。
如果你没有为查询中的命名参数提供一个匹配的@Param ,那么在编译时就会抛出一个异常。
@Query("SELECT c FROM Client c WHERE c.name = :name and c.age = :age")
List<Client> findByName(@Param("name") String name, @Param("num1") int age);
结果是。
java.lang.IllegalStateException: Using named parameters for method public abstract ClientRepository.findByName(java.lang.String,int) but parameter 'Optional[num1]' not found in annotated query 'SELECT c FROM Client c WHERE c.name = :name and c.age = :age'!
带有_@Query_注解的SpEL表达式
SpEL (Spring Expression Language)是一种支持查询的语言,并在运行时对对象图进行操作。
#{expression}
可以在
@Query注释中使用的最有用的SpEL表达式是**#{#entityname}。**
如其名所示,它表示你所在的资源库所引用的实体名称。它避免了说明实际的实体名称,并按如下方式解析。
如果域类型在
@Entity注解上设置了名称属性,那么它就被使用。否则,将使用域类型的简单类名。
换句话说。
// #{#entityName} resolves to "client_entity"
@Entity(name = "client_entity")
public class Client {}
// #{#entityName} resolves to "Client"
@Entity()
public class Client {}
这使得我们有可能将查询抽象为。
public interface ClientRepository extends JpaRepository<Client, Long> {
@Query("select e from #{#entityName} e where e.name = ?1")
List<Client> findByName(String name);
}
修改查询
DELETE 和UPDATE 查询被称为_修改性查询_,必须携带一个额外的注解:@Modifying 。这将触发@Query 注释中的查询,使其能够对实体进行修改,而不仅仅是检索数据。
如果没有额外的@Modifying 注解,我们就会面对一个InvalidDataAccessApiUsageException ,让我们知道@Query 注解不支持 DML(数据操作语言语句。
@Modifying
@Query(“DELETE c FROM Client c WHERE c.name = :name”)
void deleteClientByName(@Param("name") String name);
@Modifying
@Query(“UPDATE Client c WHERE c.id = :id”)
void updateUserById(@Param("id") long id);
排序和分页
排序和分页都可以用与派生查询相同的方式进行。
要创建Pageable和Sortable查询,你要向方法提供
Pageable参数,如果你至少扩展了PagingAndSortingRepository接口,Spring Data JPA就会自动拾取这个参数。
JpaRepository 已经扩展了 PagingAndSortingRepository ,所以该功能是固有的。
@Repository
public interface ClientRepository extends JpaRepository<Client, Long> {
@Query("select e from #{#entityName} e where e.organization = ?1")
Page<Client> findByOrganization(String name, Pageable pageable);
}
方法的返回类型变成了Page<T> 、List<T> 或Slice<T> ,不过,Page<T> 是唯一一个可以跟踪所有检索到的实体和分页结果的方法。我们现在应该传入的Pageable ,被构造成一个PageRequest.of(int page, int size, Sort sort) 。
我们提供我们正在寻找的_页面_,页面的大小以及里面的数据如何排序。
@RestController
public class Controller {
@Autowired
private ClientRepository clientRepository;
@GetMapping(value = "/", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity main() {
clientRepository.save(new Client(1, 22, "David", "StackAbuse"));
clientRepository.save(new Client(2, 34, "John", "StackAbuse"));
clientRepository.save(new Client(3, 46, "Melissa", "StackAbuse"));
Pageable pageRequest = PageRequest.of(0, 10, Sort.by("age").descending());
Page<Client> clientPage = clientRepository.findByOrganization("StackAbuse", pageRequest);
List<Client> clientList = clientPage.getContent();
int pageNum = clientPage.getNumber();
long numOfClients = clientPage.getTotalElements();
long totalNumOfPages = clientPage.getTotalPages();
return ResponseEntity.ok(
String.format("Clients: %s, \nCurrent page: %s out of %s, \nTotal entities: %s",
clientList,
pageNum,
totalNumOfPages,
numOfClients));
}
如果我们看一下日志,我们会看到Hibernate查询的启动,以及它们是如何被转换的。
2021-08-13 23:16:06.701 DEBUG 4252 --- [nio-8080-exec-1] org.hibernate.SQL : select client0_.id as id1_0_0_, client0_.age as age2_0_0_, client0_.name as name3_0_0_, client0_.organinzation as organinz4_0_0_ from client client0_ where client0_.id=?
2021-08-13 23:16:06.727 DEBUG 4252 --- [nio-8080-exec-1] org.hibernate.SQL : insert into client (age, name, organinzation, id) values (?, ?, ?, ?)
2021-08-13 23:16:06.732 DEBUG 4252 --- [nio-8080-exec-1] org.hibernate.SQL : select client0_.id as id1_0_0_, client0_.age as age2_0_0_, client0_.name as name3_0_0_, client0_.organinzation as organinz4_0_0_ from client client0_ where client0_.id=?
2021-08-13 23:16:06.733 DEBUG 4252 --- [nio-8080-exec-1] org.hibernate.SQL : insert into client (age, name, organinzation, id) values (?, ?, ?, ?)
2021-08-13 23:16:06.734 DEBUG 4252 --- [nio-8080-exec-1] org.hibernate.SQL : select client0_.id as id1_0_0_, client0_.age as age2_0_0_, client0_.name as name3_0_0_, client0_.organinzation as organinz4_0_0_ from client client0_ where client0_.id=?
2021-08-13 23:16:06.735 DEBUG 4252 --- [nio-8080-exec-1] org.hibernate.SQL : insert into client (age, name, organinzation, id) values (?, ?, ?, ?)
2021-08-13 23:16:06.754 DEBUG 4252 --- [nio-8080-exec-1] org.hibernate.SQL : select client0_.id as id1_0_, client0_.age as age2_0_, client0_.name as name3_0_, client0_.organinzation as organinz4_0_ from client client0_ where client0_.organinzation=? order by client0_.age desc limit ?
点击REST端点会导致。
Clients: [Client{id=3, age=46, name='Melissa', organization='StackAbuse'}, Client{id=2, age=34, name='John', organization='StackAbuse'}, Client{id=1, age=22, name='David', organization='StackAbuse'}],
Current page: 0 out of 1,
Total entities: 3
**注意:**你也可以在查询本身中对数据进行排序,然而,在某些情况下,简单地使用动态输入的Sort 对象会更容易。
总结
派生查询方法是Spring Data JPA的一个伟大的查询约束功能,但其简单性是以可扩展性为代价的。尽管很灵活,但对于扩展到复杂的查询来说,它们并不理想。
这就是Spring Data JPA的
@Query注解发挥作用的地方。
在本指南中,我们看了一下@Query 注解,以及如何在基于Spring的应用程序中利用它来为你的存储库编写自定义的本地和JPQL查询。
我们探讨了参数化选项,以及如何对数据进行分页和排序。