当前位置:网站首页>@Query 疑难杂症

@Query 疑难杂症

2022-06-26 00:33:00 InfoQ

作者:Damon
博客:
http://www.damon8.cn
交个朋友之猿天地 | 微服务 | 容器化 | 自动化

快速体验 @Query 的方法

开始之前,首先来看一个 Demo,沿用我们之前的例子,新增一个 @Query 的方法,快速体验一下 @Query 的使用方法,如下所示:

package com.example.jpa.example1;

import org.springframework.data.jpa.repository.JpaRepository;

import org.springframework.data.jpa.repository.Query;

import org.springframework.data.repository.query.Param;

public interface UserDtoRepository extends JpaRepository<User,Long> {

 //通过query注解根据name查询user信息

 @Query(&quot;From User where name=:name&quot;)

 User findByQuery(@Param(&quot;name&quot;) String nameParam);

}

然后,我们新增一个测试类:

package com.example.jpa.example1;

import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;

@DataJpaTest

public class UserRepositoryQueryTest {

 @Autowired

 private UserDtoRepository userDtoRepository;

 @Test

 public void testQueryAnnotation() {

//新增一条数据方便测试 userDtoRepository.save(User.builder().name(&quot;jackxx&quot;).email(&quot;[email protected]&quot;).sex(&quot;man&quot;).address(&quot;shanghai&quot;).build());

 //调用上面的方法查看结果

 User user2 = userDtoRepository.findByQuery(&quot;jack&quot;);

 System.out.println(user2);

 }

}

最后,看到运行的结果如下:

Hibernate: insert into user (address, email, name, sex, version, id) values (?, ?, ?, ?, ?, ?)

Hibernate: select user0_.id as id1_0_, user0_.address as address2_0_, user0_.email as email3_0_, user0_.name as name4_0_, user0_.sex as sex5_0_, user0_.version as version6_0_ from user user0_ where user0_.name=?

User(id=1, name=jack, [email protected], version=0, sex=man, address=shanghai)

通过上面的例子我们发现,这次不是通过方法名来生成查询语法,而是 @Query 注解在其中起了作用,使 &quot;From User where name=:name&quot;JPQL 生效了。那么它的实现原理是什么呢?我们通过源码来看一下。

JpaQueryLookupStrategy 关键源码剖析

我们在 03 课时已经介绍过 QueryLookupStrategy 的策略值有哪些,那么我们来看下源码是如何起作用的。

我们先打开 QueryExecutorMethodInterceptor 类,找到如下代码:

null
再运行上面的测试用例,这时候在这里设置一个断点,可以看到默认的策略是CreateIfNotFound,也就是如果有@Query注解,那么以@Query的注解内容为准,可以忽略方法名。

我们继续往后面看,进入到&nbsp;lookupStrategy.resolveQuery 里面,如下所示:

null
通过上图的断点和红框之处,我们也发现了,Spring Data JPA 这个地方使用了策略、模式,当我们自己写策略模式的时候也可以进行参考。

那么接着往下 debug,进入到 resolveQuery 方法里面,如下图所示:

null
我们可以看到图中 ①处,如果 Query 注解找到了,就不会走到 ② 处了(即我们第 03 课时中讲的 Defined Query Method 语法)。

这时我们点开 Query 里面的 Query 属性的值看一下,你会发现这里同时生成了两个 SQL:一个是查询总数的 Query 定义,另一个是查询结果 Query 定义。

到这里我们已经基本明白了,如果想看看 Query 具体是怎么生成的、上面的 @Param 注解是怎么生效的,可以在上面的图 ① 处 debug 继续往里面看,如下所示:

null
我们继续一路 debug 就可以看到怎么通过 @Query 去生成 SQL 了,这个不是本节的重点,我在这里就简单带过了,你有兴趣可以自己去 debug 看一下。

那么原理我们掌握了,接下来看看 @Query 给我们提供了哪些语法吧,先看下基本用法。

@Query 的基本用法

在讲解它的语法之前,我们看一下它的注解源码,了解一下基本用法。

package org.springframework.data.jpa.repository;

public @interface Query {

 /**

 * 指定JPQL的查询语句。(nativeQuery=true的时候,是原生的Sql语句)

 */

 String value() default &quot;&quot;;

 /**

 * 指定count的JPQL语句,如果不指定将根据query自动生成。

 * (如果当nativeQuery=true的时候,指的是原生的Sql语句)

 */

 String countQuery() default &quot;&quot;;

 /**

 * 根据哪个字段来count,一般默认即可。

 */

 String countProjection() default &quot;&quot;;

 /**

 * 默认是false,表示value里面是不是原生的sql语句

 */

 boolean nativeQuery() default false;

 /**

 * 可以指定一个query的名字,必须唯一的。

 * 如果不指定,默认的生成规则是:

 * {$domainClass}.${queryMethodName}

 */

 String name() default &quot;&quot;;

 /*

 * 可以指定一个count的query的名字,必须唯一的。

 * 如果不指定,默认的生成规则是:

 * {$domainClass}.${queryMethodName}.count

 */

 String countName() default &quot;&quot;;

}

所以到这里你会发现, @Query 用法是使用 JPQL 为实体创建声明式查询方法。我们一般只需要关心 @Query 里面的 value 和 nativeQuery、countQuery 的值即可,因为其他的不常用。

使用声明式 JPQL 查询有个好处,就是启动的时候就知道你的语法正确不正确。那么我们简单介绍一下 JPQL 语法。
JPQL 的语法
我们先看一下查询的语法结构,代码如下:

SELECT ... FROM ...

[WHERE ...]

[GROUP BY ... [HAVING ...]]

[ORDER BY ...]

你会发现它的语法结构有点类似我们 SQL,唯一的区别就是 JPQL FROM 后面跟的是对象,而 SQL 里面的字段对应的是对象里面的属性字段。

同理我们看一下 update 和 delete 的语法结构:

DELETE FROM ... [WHERE ...]

 

UPDATE ... SET ... [WHERE ...]

其中“...”省略的部分是实体对象名字和实体对象里面的字段名字,而其中类似 SQL 一样包含的语法关键字有:SELECT&nbsp; FROM&nbsp; WHERE&nbsp; UPDATE&nbsp; DELETE&nbsp; JOIN&nbsp; OUTER&nbsp; INNER&nbsp; LEFT&nbsp; GROUP&nbsp; BY&nbsp; HAVING&nbsp; FETCH&nbsp; DISTINCT&nbsp; OBJECT&nbsp; NULL&nbsp; TRUE&nbsp; FALSE&nbsp; NOT&nbsp; AND&nbsp; OR&nbsp; BETWEEN&nbsp; LIKE&nbsp; IN&nbsp; AS&nbsp; UNKNOWN&nbsp; EMPTY&nbsp; MEMBER&nbsp; OF&nbsp; IS&nbsp; AVG&nbsp; MAX&nbsp; MIN&nbsp; SUM&nbsp; COUNT&nbsp; ORDER&nbsp; BY&nbsp; ASC&nbsp; DESC&nbsp; MOD&nbsp; UPPER&nbsp; LOWER&nbsp; TRIM&nbsp; POSITION&nbsp; CHARACTER_LENGTH&nbsp; CHAR_LENGTH&nbsp; BIT_LENGTH&nbsp; CURRENT_TIME&nbsp; CURRENT_DATE&nbsp; CURRENT_TIMESTAMP&nbsp; NEW&nbsp; EXISTS&nbsp; ALL&nbsp; ANY&nbsp; SOME 这么多,我们就不一一介绍了。
原网站

版权声明
本文为[InfoQ]所创,转载请带上原文链接,感谢
https://xie.infoq.cn/article/893fcdea7e9a99056de7f1303