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 中使用基于类的投影时,💥~。

到此这篇关于Spring Data JPA 在 @Query 中使用投影的方法的文章就介绍到这了,更多相关Spring Data JPA 投影内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Spring Data JPA 在 @Query 中使用投影的方法示例详解的更多相关文章

  1. canvas中普通动效与粒子动效的实现代码示例

    canvas用于在网页上绘制图像、动画,可以将其理解为画布,在这个画布上构建想要的效果。本文详细的介绍了粒子特效,和普通动效进行对比,非常具有实用价值,需要的朋友可以参考下

  2. H5混合开发app如何升级的方法

    本篇文章主要介绍了H5混合开发app如何升级的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  3. canvas学习和滤镜实现代码

    这篇文章主要介绍了canvas学习和滤镜实现代码,利用 canvas,前端人员可以很轻松地、进行图像处理,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  4. localStorage的过期时间设置的方法详解

    这篇文章主要介绍了localStorage的过期时间设置的方法详解的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  5. 详解HTML5 data-* 自定义属性

    这篇文章主要介绍了详解HTML5 data-* 自定义属性的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  6. HTML5的postMessage的使用手册

    HTML5提出了一个新的用来跨域传值的方法,即postMessage,这篇文章主要介绍了HTML5的postMessage的使用手册的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  7. 教你使用Canvas处理图片的方法

    本篇文章主要介绍了教你使用Canvas处理图片的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  8. ios – Swift语言:如何调用SecRandomCopyBytes

    从Objective-C,我可以这样做:在Swift中尝试这个时,我有以下内容:但我得到这个编译器错误:data.mutableBytes参数被拒绝,因为类型不匹配,但我无法弄清楚如何强制参数.解决方法这似乎有效:

  9. 使用Firebase iOS Swift将特定设备的通知推送到特定设备

    我非常感谢PushNotifications的帮助.我的应用聊天,用户可以直接向对方发送短信.但是如果没有PushNotifications,它就没有多大意义.它全部设置在Firebase上.如何将推送通知从特定设备发送到特定设备?

  10. ios – NSData to Data swift 3

    如何将此代码转换为使用Swift3数据?

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部