Spring Data JPA 在 @Query 中使用投影的方法

关于投影的基本使用可以参考这篇文章:https://www.baeldung.com/spring-data-jpa-projections。下文沿用了这篇文章中的示例代码。

投影的官方文档链接是:https://docs.spring.io/spring-data/jpa/docs/2.6.5/reference/html/#projections (我这里使用的是 2.6.5 的版本)。

背景铺垫完毕,接下来开始正文。

最近在写需求的时候用到了投影来减少数据库查询的字段,结果发现官方文档中挖了个坑= =。官方文档中以及另一篇示例文章中,全程使用了方法名派生的查询方式,而投影的文档中却全程没有提到示例的内容方法名派生的查询方式下才有效。

那么,方法名派生的查询方式好用吗?对于简单的只有两三个字段的查询来说,确实方便好用,但条件一多,问题就来了,如果有五六个字段要过滤,那方法名简直长的不能看,并且很多查询默认值都需要通过参数传进来而不是直接内置到 SQL 中。

在这种时候我更偏好使用自定义查询的方式,直接面向 SQL 编程,比看巨长的方法名要容易的多。

当我在这次需求中把投影和自定义查询一结合,这坑它就来了...

上面提过,使用投影是为了减少数据库查询的字段。而直接运行示例代码的时候也确实看到了这个效果:

测试代码

@Test
public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned() {
PersonView personView = personRepository.findByLastName("Doe");
} public interface PersonView { String getLastName(); } @Entity
public class Person {
@Id
private Long id;
private String firstName;
private String lastName;
}

执行的 SQL

select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?

然后当我换成自定义查询的方式时,效果就变成了这样:

测试代码

@Query("select p from Person p where p.lastName = ?1")
PersonView findByLastNameByQuery(String lastName); @Test
public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() {
PersonView personView = personRepository.findByLastNameByQuery("Doe");
}

执行的SQL

select person0_.id as id1_6_, person0_.first_name as first_na2_6_, person0_.last_name as last_nam3_6_ from person person0_ where person0_.last_name=?

可以看到这里是查询了全部的字段(实在是让人摸不着头脑)。

后来有同事提醒说是因为我写了select p导致的,我就尝试写明要查询的字段(但还是无法理解为什么在这种情况下投影直接不生效):

测试代码

@Query("select p.lastName from Person p where p.lastName = ?1")
PersonView findByLastNameByQuery(String lastName);

执行的 SQL

select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?

从 SQL 上来看,这样写已经是实现了我想要的效果,可是实际上真正使用这个代码的时候,坑就又来了:

测试代码

@Test
public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() {
PersonView personView = personRepository.findByLastNameByQuery("Doe");
assertThat(personView.getLastName()).isEqualTo("Doe");
}

加了一行断言来模拟使用的场景

执行结果

org.opentest4j.AssertionFailedError:
expected: "Doe"
but was: null
Expected :"Doe"
Actual :null

直接黑人问号脸。

分析了一下,执行的 SQL 没有问题,投影类也没有问题,那问题就是出在结果集映射的时候了。虽然没看过 JPA 的代码,但是最终肯定是基于 JDBC API 的,而JDBC API是怎么处理结果集映射的?

翻一翻 ResultSet 类可以看到一共有两种方法获取结果:by indexby name,仔细看看执行的 SQL,person0_.last_name as col_0_0_ last_name 自动生成了一个别名叫col_0_0_,而投影类中能获得的信息只有字段名last_name而没有别名col_0_0_,所以 by name 的路走不通;

那么by index呢,很明显也不行,我这里的示例只有一个字段,假如有两个字段,那么SQL 中的字段的顺序和投影类中的字段的顺序就无法保证一致,从而就无法根据 index 来获取想要的对应的结果。

然后就是验证环节了,假如是因为名字映射不上导致的结果为 null,那我就给你一个能对应的名字:

测试代码

@Query("select p.lastName as lastName from Person p where p.lastName = ?1")
PersonView findByLastNameByQuery(String lastName); @Test
public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned2() {
PersonView personView = personRepository.findByLastNameByQuery("Doe");
assertThat(personView.getLastName()).isEqualTo("Doe");
}

执行的 SQL

select person0_.last_name as col_0_0_ from person person0_ where person0_.last_name=?

虽然执行的 SQL 上还是用了自动生成的别名,但是断言却通过了,猜测是 JPA 在解析 Query 的时候存储了手动声明的别名信息。

最后总结一下,如果要在 @Query 中使用投影,必须要主动声明要查询的字段,并且主动写明字段的别名才行。

最后的最后,再吐槽一下 JPA,文档中提到投影除了基于接口之外,还可以基于类来实现,然鹅当你想在 @Query 中使用基于类的投影时,~。

最新文章

  1. CSS 概念 & 作用
  2. thinkphp笔记
  3. 从零开始--系统深入学习IOS(使用Swift---带链接)
  4. 机器学习算法 Python & R速查表
  5. SecureCRT issue "Could not open clipboard: Assess is denied" (无法打开粘贴板:访问被拒绝)
  6. Entity Framework 全面教程详解(转)
  7. Windows Phone 开发起步之旅之二 C#中的值类型和引用类型
  8. 用一行代码初始化ArrayList
  9. Java API —— 反射
  10. 深入浅出java并发
  11. python paramiko模拟ssh登录,实现sftp上传或者下载文件
  12. "hello,world"———C++入门有感
  13. [SEO基础知识] html标签优化 (摘抄)
  14. Maven中的-D(Properties属性)和-P(Profiles配置文件)
  15. django 中下载文件与下载保存为excel
  16. 软件测试为何我会首选Python
  17. 定制jQuery File Upload为微博式单文件上传
  18. 2018.09.16 loj#10241. 取石子游戏 1(博弈论)
  19. 使用css实现炫酷的横屏滚动效果
  20. 使用Cache缓存

热门文章

  1. FreeRTOS --(3)内存管理 heap2
  2. Django学习——路由层之路由匹配、无名分组、有名分组、反向解析
  3. 排序算法详解(java代码实现)
  4. 记一次burp suite文件上传漏洞实验
  5. ucore lab2 物理内存管理 学习笔记
  6. SSH 证书登录教程
  7. sklearn机器学习实战-KNN
  8. Java 16 新特性:record类
  9. OAuth2密码模式已死,最先进的Spring Cloud认证授权方案在这里
  10. spring aop 记录 service 方法调用时长 - 环绕通知