写点什么

@Query 疑难杂症

作者:Xiao8
  • 2022 年 6 月 25 日
  • 本文字数:2669 字

    阅读完需:约 9 分钟

作者: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("From User where name=:name")
User findByQuery(@Param("name") 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("jackxx").email("123456@126.com").sex("man").address("shanghai").build());
//调用上面的方法查看结果
User user2 = userDtoRepository.findByQuery("jack");
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=123456@126.com, version=0, sex=man, address=shanghai)
复制代码


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

JpaQueryLookupStrategy 关键源码剖析

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


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



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


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



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


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



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


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


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



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


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

@Query 的基本用法

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


package org.springframework.data.jpa.repository;
public @interface Query {
/**
* 指定JPQL的查询语句。(nativeQuery=true的时候,是原生的Sql语句)
*/
String value() default "";
/**
* 指定count的JPQL语句,如果不指定将根据query自动生成。
* (如果当nativeQuery=true的时候,指的是原生的Sql语句)
*/
String countQuery() default "";
/**
* 根据哪个字段来count,一般默认即可。
*/
String countProjection() default "";
/**
* 默认是false,表示value里面是不是原生的sql语句
*/
boolean nativeQuery() default false;
/**
* 可以指定一个query的名字,必须唯一的。
* 如果不指定,默认的生成规则是:
* {$domainClass}.${queryMethodName}
*/
String name() default "";
/*
* 可以指定一个count的query的名字,必须唯一的。
* 如果不指定,默认的生成规则是:
* {$domainClass}.${queryMethodName}.count
*/
String countName() default "";
}
复制代码


所以到这里你会发现, @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  FROM  WHERE  UPDATE  DELETE  JOIN  OUTER  INNER  LEFT  GROUP  BY  HAVING  FETCH  DISTINCT  OBJECT  NULL  TRUE  FALSE  NOT  AND  OR  BETWEEN  LIKE  IN  AS  UNKNOWN  EMPTY  MEMBER  OF  IS  AVG  MAX  MIN  SUM  COUNT  ORDER  BY  ASC  DESC  MOD  UPPER  LOWER  TRIM  POSITION  CHARACTER_LENGTH  CHAR_LENGTH  BIT_LENGTH  CURRENT_TIME  CURRENT_DATE  CURRENT_TIMESTAMP  NEW  EXISTS  ALL  ANY  SOME 这么多,我们就不一一介绍了。

发布于: 刚刚阅读数: 3
用户头像

Xiao8

关注

God bless the fighters. 2020.03.11 加入

欢迎关注公众号:程序猿Damon,长期从事Java开发,研究Springcloud的微服务架构设计。目前主要从事基于K8s云原生架构研发的工作,Golang开发,长期研究边缘计算框架KubeEdge、调度框架Volcano、容器云KubeSphere研究

评论

发布
暂无评论
@Query 疑难杂症_6月月更_Xiao8_InfoQ写作社区