@Query 疑难杂症

简介: 关于注解Query的一些疑难问题谈一点

快速体验 @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 关键源码剖析

那么我们来看下源码是如何起作用的

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

image.png

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

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

image.png

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

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

image.png

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

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

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

image.png

我们继续一路 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 这么多,我们就不一一介绍了。

相关文章
|
JavaScript 前端开发
javascript 如何判断字符串日期是否相差七天
在JavaScript中,你可以使用`Date`对象来比较两个日期之间的差异。下面是一个简单的示例,演示如何判断两个字符串日期是否相差七天: ```javascript function isSevenDaysDifference(date1, date2) { // 确保输入是字符串 if (typeof date1 !== 'string' || typeof date2 !== 'string') { return false; } // 将字符串转换为Date对象 var d1 = new Date(date1);
399 1
|
消息中间件 Java
SpringBoot RabbitMQ死信队列
SpringBoot RabbitMQ死信队列
345 0
|
9月前
|
SQL Web App开发 数据可视化
2025年最热门的TOP5报表工具排行榜
很多数据项目中都会用报表工具来做报表,怎么选一款好用的工具一直是个难题,今天,我们一起盘点5款热门报表软件,看看每款报表工具的特点,看看哪款更适合您。
|
机器学习/深度学习 人工智能 自然语言处理
大模型强崩溃!Meta新作:合成数据有剧毒,1%即成LLM杀手
在人工智能领域,大型语言模型(LLMs)的快速发展令人瞩目,但递归生成数据可能导致“模型崩溃”。Meta的研究揭示,模型在训练过程中会逐渐遗忘低概率事件,导致数据分布偏差。即使少量合成数据(如1%)也会显著影响模型性能,最终导致崩溃。研究强调保留原始数据的重要性,并提出社区合作和技术手段来区分合成数据和真实数据。论文地址:https://www.nature.com/articles/s41586-024-07566-y
414 2
|
机器学习/深度学习 人工智能 数据安全/隐私保护
探索iOS应用开发的未来趋势
本文深入剖析了iOS应用开发的最新动态和未来趋势,从SwiftUI的革新到App Clips的潜力,再到人工智能与机器学习的融合,以及隐私保护的重要性。文章不仅为开发者提供了宝贵的行业洞察,还展望了iOS平台即将到来的技术革命,旨在帮助读者把握未来的发展方向并提前做好准备。
324 27
|
编解码 UED iOS开发
苹果mac系统音频播放软件哪个好一点
在苹果mac系统上,有许多不同的音频播放软件可供选择,这些软件各有优点和适用场景。下面将为您详细介绍每一个软件,帮助您找到最适合您的音频播放器。
799 1
苹果mac系统音频播放软件哪个好一点
|
网络协议 网络安全 程序员
socket,tcp,http三者之间的原理和区别
socket,tcp,http三者之间的原理和区别
socket,tcp,http三者之间的原理和区别
|
消息中间件 Java Kafka
flink问题之在通过TableFunction实现行转列时Row一直是空如何解决
Apache Flink是由Apache软件基金会开发的开源流处理框架,其核心是用Java和Scala编写的分布式流数据流引擎。本合集提供有关Apache Flink相关技术、使用技巧和最佳实践的资源。
542 1
|
安全 Cloud Native 算法
云原生安全-云计算发展白皮书(2020年)解读
云原生安全-云计算发展白皮书(2020年)解读
370 0
|
存储
【PCIe 6.0】缘起缘灭缘终尽,花开花落花归尘——缅怀被PCIe 6.0拿掉的LN(Lightweight Notification)协议
【PCIe 6.0】缘起缘灭缘终尽,花开花落花归尘——缅怀被PCIe 6.0拿掉的LN(Lightweight Notification)协议
710 0
【PCIe 6.0】缘起缘灭缘终尽,花开花落花归尘——缅怀被PCIe 6.0拿掉的LN(Lightweight Notification)协议