# 光速上手 - JPA 原生 sql DTO 投影


                                                                     前言

        使用 JPA 时,我们一般通过 @Entity 进行实体类映射,从数据库中查询出对象。然而,在实际开发中,有时需要自定义查询结果并将其直接映射到 DTO,而不是实体类。这种需求可以通过 JPA 原生 SQL 查询和 DTO 投影 来实现。博主将以实际开发场景 为例,快速摘要如何在 JPA 中实现基于原生 SQL 的 DTO 投影



                                                                    开始 - 实现步骤

                                   以下是实现 DTO 投影的完整步骤,包括实体类、SQL 映射配置、接口调用和 DTO 设计。



一、配置实体类及映射

       首先在实体类中定义 @SqlResultSetMapping,用于将原生 SQL 查询结果映射到 DTO 类。在这个例子中,我们定义了 IssueVideo 实体,并通过 @SqlResultSetMapping@NamedNativeQuery 配置了一个纯sql查询


两个注解 详解 (已理解可以跳过)

  • 1. @NamedNativeQuery

    核心作用

    • @NamedNativeQuery 是用来定义 原生 SQL 查询 的。

    • 尽管JPA 中已经为sql 提供了许多方便的解决方式,但是某些场景下,我们还是需要直接使用原生 SQL , 例如:

      1. 数据查询逻辑复杂,无法用 JPQL 表达
      2. 涉及数据库特定的功能(如窗口函数、分区排序等)
      3. 查询结果无法直接映射到实体类(如 DTO、聚合结果)

       通过 @NamedNativeQuery,我们可以直接在实体类中绑定一个原生 SQL 查询,并为这个查询命名。在调用时,可以通过指定这个命名的查询名称直接执行该 SQL


  • 2. @SqlResultSetMapping
    核心作用

    • @SqlResultSetMapping 是用来定义 查询结果的映射 规则的。

    • 当我们使用原生 SQL 查询时,返回的结果是数据库的行列数据,与实体类的属性或 DTO 的结构未必完全匹配。

      @SqlResultSetMapping(name = "", ...) -> 它长这样
      

      它用来告诉 JPA:

      • 查询返回的列和 DTO 的字段如何一一对应
      • 如何将原生 SQL 查询的结果映射为自定义的类(DTO)

      ​         没有 @SqlResultSetMapping 时,JPA 会尝试将查询结果映射到实体类,但如果结果不是直接对应实体类那么映射就会失败。这时,我们就需要 @SqlResultSetMapping 来自定义映射规则。


  • 3. 为什么需要将它们写在实体类上?

    • 实体类是 SQL 映射的入口
             在 JPA 中,实体类是我们与数据库表交互的核心对象。因此:

      • @NamedNativeQuery@SqlResultSetMapping 写在实体类上,可以明确这段查询与该实体相关,方便维护和查阅。

      • JPA 的原生查询和结果映射机制依赖于实体类的原数据,通过注解绑定的方式,可以让这些查询和映射规则作为实体类的一部分,便于复用。

    • 关联性强

      • @NamedNativeQuery
               定义了原生 SQL 查询,而 @SqlResultSetMapping 定义了如何映射这个查询的结果,它们是 成对使用的。二者写在同一个实体类上,能清晰地表达“该查询和该实体类相关”的逻辑。

      • 如果你把它们分散到其他地方,可能会增加代码复杂性和维护成本。


  • 4. 总结

简单来说,@NamedNativeQuery@SqlResultSetMapping 分别解决了两个不同的问题:

  • @NamedNativeQuery 负责“如何查询
           它定义了 SQL 的逻辑以及参数。
  • @SqlResultSetMapping 负责“如何处理查询结果
           它定义了如何将 SQL 返回的数据映射到 DTO中

二者通过查询名称(name 属性)联系在一起。例如:

@NamedNativeQuery(
    name = "IssueRecommendRespDTOQuery", // 查询名称
    resultSetMapping = "IssueRecommendRespDTOResult", // 映射↓
    query = """
        SELECT ... -- 原生 SQL 查询
    """
)
@SqlResultSetMapping(
    name = "IssueRecommendRespDTOResult", // 映射名称 对应↑
    classes = @ConstructorResult(
        targetClass = IssueRecommendRespDTO.class, // 映射到的 DTO
        columns = {
            @ColumnResult(name = "isdId", type = Integer.class),
            ...
        }
    )
)

两个注解解释 - end



实体类代码示例
@AllArgsConstructor
@NoArgsConstructor
@Data
@Entity
@Table(name = "issue_video")
// region jpa 投影主页查询目标dto (可以通过 "region <> endregion" 将其折叠起来)
@SqlResultSetMapping(
    name = "IssueRecommendRespDTOResult",
    classes = @ConstructorResult(
        targetClass = IssueRecommendRespDTO.class,
        columns = {
            @ColumnResult(name = "isdId", type = Integer.class),
            @ColumnResult(name = "videoUrl", type = String.class),
            @ColumnResult(name = "duration", type = Integer.class),
            @ColumnResult(name = "issId", type = Integer.class),
            @ColumnResult(name = "title", type = String.class),
            @ColumnResult(name = "cover", type = String.class),
            @ColumnResult(name = "watchNum", type = BigInteger.class),
            @ColumnResult(name = "commentNum", type = Integer.class),
            @ColumnResult(name = "creTime", type = LocalDateTime.class),
            @ColumnResult(name = "authorId", type = Integer.class),
            @ColumnResult(name = "authorName", type = String.class)
        }
    )
)
// endregion

// region jpa 投影主页推荐查询sql
@NamedNativeQuery(
    name = "IssueRecommendRespDTOQuery",
    resultSetMapping = "IssueRecommendRespDTOResult",
    query = """
SELECT
        sub.isd_id AS isdId,
        sub.video_url AS videoUrl,
        sub.duration AS duration,
        sub.iss_id AS issId,
        sub.title AS title,
        sub.cover AS cover,
        sub.watch_num AS watchNum,
        sub.comment_num AS commentNum,
        sub.cre_time AS creTime,
        sub.u_id AS authorId,
        sub.name AS authorName
      FROM (
               SELECT
                   i.score,
                   v.isd_id,
                   v.video_url,
                   v.duration,
                   i.iss_id,
                   i.title,
                   i.cover,
                   i.watch_num,
                   i.comment_num,
                   i.cre_time,
                   author.u_id,
                   author.name,
                   case
                       when ROW_NUMBER() OVER (
                           PARTITION BY i.su_id
                           ORDER BY
                               CASE
                                   WHEN sp.su_id IS NOT NULL THEN (i.score + sp.score)
                                   ELSE i.score
                                   END DESC
                           ) <= 3 then 1
                      else 2
                      end AS rank_within_partition,
                   RANK() OVER (
                       PARTITION BY i.su_id
                       ORDER BY
                           CASE
                               WHEN sp.su_id IS NOT NULL THEN (i.score + sp.score)
                               ELSE i.score
                               END DESC
                       ) as global_rank
               FROM issue_video v
                        JOIN issue i ON i.iss_id = v.iss_id
                        JOIN user author ON author.u_id = i.u_id
                        LEFT JOIN subarea_preference sp
                            ON sp.su_id = i.su_id
                            and sp.u_id = :uId
              order by (i.score + sp.score) desc
           ) sub
      order by rank_within_partition, global_rank
"""
)
// endregion
public class IssueVideo {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    private Integer isdId;

    private String videoUrl;
    private int duration;
    private int size;
    private LocalDateTime updTime;
    private String remark;
    private LocalDateTime issueTime;
    private String permission;
    private Boolean isDeclare;
    private Boolean offDanmu;
    private Boolean offComm;
    private Boolean onGretestComm;
    private Boolean isDel;

    @ManyToOne
    @JoinColumn(name = "vd_id")
    private VideoDeclare videoDeclare;

    private BigInteger danmuNum;

    @OneToOne
    @JoinColumn(name = "iss_id")
    private Issue issue;
}

二、配置对应 JPA 接口

       在 JPA 接口中,直接通过 @Query 注解调用刚刚定义的原生 SQL 查询,并将结果映射成期望的 DTO 返回类型

Repository 代码示例
public interface IssueVideoRepo extends JpaRepository<IssueVideo, Integer> {
    /**
     * 获取主页推荐视频
     * @param uId 用户 ID
     * @return DTO 列表
     */
    @Query(name = "IssueRecommendRespDTOQuery", nativeQuery = true)
    List<IssueRecommendRespDTO> getRecommendVideos(@Param("uId") Integer uId);
}

三、事务层调用接口

       事务层负责调用 Repository 接口,并将返回的结果处理为最终服务层需要的数据。以下为服务实现代码:

Service 代码示例
@Service
public class IssueVideoServiceImpl implements IssueVideoService {
    @Autowired
    private IssueVideoRepo issueVideoRepo;

    @Override
    public List<IssueRecommendRespDTO> getRecommendVideos(Integer uId) {
        List<IssueRecommendRespDTO> recommendVideos = null;
        try {
            recommendVideos = issueVideoRepo.getRecommendVideos(uId);
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return recommendVideos;
    }
}

四、定义 DTO 类

       最后,定义与查询结果对应的 DTO 类。DTO 结构需要与 @SqlResultSetMapping 中的字段一一对应。

DTO 代码示例
@Data // 主dto 不需要lombok 全参数注解 @AllArgsConstructor, 因为要额外配置
public class IssueRecommendRespDTO {
    private Integer isdId;
    private String videoUrl;
    private Integer duration;
    private IssueRecommendInDTO issue;

    public IssueRecommendRespDTO() {}

    public IssueRecommendRespDTO(Integer isdId, String videoUrl, Integer duration,
                                 Integer issId, String title, String cover,
                                 BigInteger watchNum, Integer commentNum, LocalDateTime creTime,
                                 Integer authorId, String authorName) {

        this.isdId = isdId;
        this.videoUrl = videoUrl;
        this.duration = duration;
        this.issue = new IssueRecommendInDTO(
                issId, title, cover,
                watchNum, commentNum, creTime,
                new AuthorInDTO(authorId, authorName)
        );
    }
}

@AllArgsConstructor
@NoArgsConstructor
@Data
public class IssueRecommendInDTO { // In命名表示 该dto 很可能处于内部使用 > internal : 内部的
    private Integer issId;
    private String title;
    private String cover;
    private BigInteger watchNum;
    private Integer commentNum;
    private LocalDateTime creTime;
    private AuthorInDTO author;
}

@AllArgsConstructor
@NoArgsConstructor
@Data
public class AuthorInDTO {
    private Integer authorId;
    private String authorName;
}

总结

       上述步骤以经过实际开发测试,保证有效!

  1. 高效性:直接通过原生 SQL 查询所需数据,减少不必要字段的查询和映射。
  2. 灵活性:可以自由定义 DTO 结构,满足复杂查询的需求。
  3. 可维护性:使用 @SqlResultSetMapping 将 SQL 与 Java 类关联,便于后续维护。

       该文章适用于需要自定义复杂查询且无需将查询结果绑定到实体类的场景。如果你也有类似需求,不妨以文章为参照上手试试!

end…

如果这篇文章帮到你, 帮忙点个关注呗, 不想那那那点赞或收藏也行鸭 (。•̀ᴗ-)✧ ~
在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述
                                                                                                                                   '(இ﹏இ`。)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/944644.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

区块链安全常见的攻击合约和简单复现,附带详细分析——不安全调用漏洞 (Unsafe Call Vulnerability)【6】

区块链安全常见的攻击分析——不安全调用漏洞 Unsafe Call Vulnerability 区块链安全常见的攻击合约和简单复现&#xff0c;附带详细分析——不安全调用漏洞 (Unsafe Call Vulnerability)【6】1.1 漏洞合约1.2 漏洞分析1.3 攻击步骤分析1.4 攻击合约 区块链安全常见的攻击合约和…

MVCC实现原理以及解决脏读、不可重复读、幻读问题

MVCC实现原理以及解决脏读、不可重复读、幻读问题 MVCC是什么&#xff1f;有什么作用&#xff1f;MVCC的实现原理行隐藏的字段undo log日志版本链Read View MVCC在RC下避免脏读MVCC在RC造成不可重复读、丢失修改MVCC在RR下解决不可重复读问题RR下仍然存在幻读的问题 MVCC是什么…

FFmpeg 4.3 音视频-多路H265监控录放C++开发二十一.4,SDP协议分析

SDP在4566 中有详细描述。 SDP 全称是 Session Description Protocol&#xff0c; 翻译过来就是描述会话的协议。 主要用于两个会话实体之间的媒体协商。 什么叫会话呢&#xff0c;比如一次网络电话、一次电话会议、一次视频聊天&#xff0c;这些都可以称之为一次会话。 那为什…

【Go】:Sentinel 动态数据源配置指南

前言 在现代微服务架构中&#xff0c;流量控制是确保系统高可用性和稳定性的关键。Sentinel 是一款由阿里巴巴开源的流量控制组件&#xff0c;它不仅支持熔断降级和流量整形&#xff0c;还能通过动态数据源&#xff08;如本地文件或 Nacos&#xff09;加载规则&#xff0c;从而…

VM虚拟机配置ubuntu网络

目录 桥接模式 NAT模式 桥接模式 特点&#xff1a;ubuntu的IP地址与主机IP的ip地址不同 第一部分&#xff1a;VM虚拟机给ubuntu的网络适配器&#xff0c;调为桥接模式 第二部分&#xff1a;保证所桥接的网络可以上网 第三部分&#xff1a;ubuntu使用DHCP&#xff08;默认&…

贝叶斯分类——数学模型

贝叶斯分类是一类分类算法的总称&#xff0c;这类算法均以贝叶斯定理为基础&#xff0c;故统称为贝叶斯分类。 贝叶斯分类是一类利用概率统计知识进行分类的算法&#xff0c;其分类原理是贝叶斯定理。贝叶斯定理是由18世纪概率论和决策论的早期研究者Thomas Bayes发明的&#…

爬虫与反爬虫实现全流程

我选取的网页爬取的是ppt nba版 需要的工具:pycharm,浏览器 爬虫需要观察它的网页信息,然后开始首先爬取它的html,可以看到有人气,标题,日期,咨询 可以看到用get方法 import requests url"https://img-home.csdnimg.cn/images/20230724024159.png?origin_urlhttps%3A%2…

云计算学习架构篇之HTTP协议、Nginx常用模块与Nginx服务实战

一.HTTP协议讲解 1.1rsync服务重构 bash 部署服务端: 1.安装服务 [rootbackup ~]# yum -y install rsync 2.配置服务 [rootbackup ~]# vim /etc/rsyncd.conf uid rsync gid rsync port 873 fake super yes use chroot no max connections 200 timeout 600 ignore erro…

【210】成绩管理系统

--基于springboot毕业设计成绩管理系统 主要功能: 个人中心 管理员管理 毕业论文管理 答辩秘书管理 基础数据管理 公告信息管理 公告信息管理 评阅教师管理 用户管理 指导教师管理 开发技术栈: 开发语言 : Java 开发软件 : Eclipse/MyEclipse/IDEA JDK版本 : JDK8…

Delphi历史版本对照及主要版本特性

Delphi编程的关键特性包括&#xff1a; 可视化开发&#xff1a;Delphi以其独特的开发方法而闻名&#xff0c;它允许开发者通过直观的表单设计器来创建用户界面。这种快速应用程序开发&#xff08;RAD&#xff09;的方法大大简化并加速了图形用户界面&#xff08;GUI&#xff09…

嵌入式系统 第九讲 设备驱动程序设计基础

• 9.1 Linux设备驱动程序简介 • 系统调用&#xff1a;是操作系统内核&#xff08;Linux系统内核&#xff09;和应用程序之间 的接口。 • 设备驱动程序&#xff1a;是操作系统内核&#xff08;Linux系统内核&#xff09;和机器硬件 之间的接口&#xff0c;设备驱动程序为应用…

算法学习(19)—— 队列与 BFS

关于bfs bfs又称宽搜&#xff0c;全称是“宽度优先遍历”&#xff0c;然后就是关于bfs的三个说法&#xff1a;“宽度优先搜索”&#xff0c;“宽度优先遍历”&#xff0c;“层序遍历”&#xff0c;这三个都是同一个东西&#xff0c;前面我们介绍了大量的深度优先遍历的题目已经…

cellphoneDB进行CCI以及可视化

除了cellchat&#xff0c;在单细胞转录组或者空间组的分析中&#xff0c;cellphoneDB也是一个常用的细胞通讯软件&#xff0c;这个数据库更注重配受体关系&#xff0c;对于有明确先验知识的配受体研究比较友好。 但值得注意的是&#xff0c;它的数据库只包括人的基因名称信息&…

003 字节码

字节码的位置 当我们讨论到字节码&#xff0c;我们需要清楚它在整个学习框架中的位置 如图&#xff0c;字节码是我们写的代码编译之后的结果&#xff0c;与虚拟机很近。 字节码是Java能实现跨平台的基础。 字节码基本知识体系 我们需要关注的点在于class文件的构成上。 字节…

基本算法——回归

本节将通过分析能源效率数据集&#xff08;Tsanas和Xifara&#xff0c;2012&#xff09;学习基本的回归算法。我们将基 于建筑的结构特点&#xff08;比如表面、墙体与屋顶面积、高度、紧凑度&#xff09;研究它们的加热与冷却负载要 求。研究者使用一个模拟器设计了12种不…

U盘文件剪切丢失的全方位解析与恢复指南

一、U盘文件剪切丢失现象描述 在日常使用U盘的过程中&#xff0c;我们时常会遇到需要将文件从一个位置移动到另一个位置的情况&#xff0c;而剪切加粘贴便是最常用的操作之一。然而&#xff0c;有时在剪切文件后&#xff0c;却意外发现目标位置并没有出现这些文件&#xff0c;…

洛谷 P1075 [NOIP2012 普及组] 质因数分解 C语言

题目&#xff1a; P1075 [NOIP2012 普及组] 质因数分解 - 洛谷 | 计算机科学教育新生态 题目描述 已知正整数 n 是两个不同的质数的乘积&#xff0c;试求出两者中较大的那个质数。 输入格式 输入一个正整数 n。 输出格式 输出一个正整数 p&#xff0c;即较大的那个质数。…

Lecture 17

10’s Complement Representation 主要内容&#xff1a; 1. 10’s 补码表示: • 10’s 补码表示法需要指定表示的数字位数&#xff08;用 n 表示&#xff09;。 • 表示的数字取决于 n 的位数&#xff0c;这会影响具体数值的解释。 2. 举例: • 如果采用 3 位补码&…

电子电器架构 --- 智能座舱HUD技术革新

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所谓鸡汤&#xff0c;要么蛊惑你认命&#xff0c;要么怂恿你拼命&#xff0c;但都是回避问题的根源&…

零基础微信小程序开发——全局配置之tabBar(保姆级教程+超详细)

&#x1f3a5; 作者简介&#xff1a; CSDN\阿里云\腾讯云\华为云开发社区优质创作者&#xff0c;专注分享大数据、Python、数据库、人工智能等领域的优质内容 &#x1f338;个人主页&#xff1a; 长风清留杨的博客 &#x1f343;形式准则&#xff1a; 无论成就大小&#xff0c;…