jpa查询返回自定义对象、返回指定VO、POJO

jpa查询返回自定义对象、返回指定VO、POJO

jpa查询返回自定义对象、返回指定VO、POJO,JPA查询前会做大量处理,还有线程通知的操作。若并发大,处理性能直线下降。但是jpa就因为做了大量处理,对多数据库兼容极好,操作方便。

有时候你想查询某个表,不想要某个字段内容太长时;或要返回非entity的对象时,需要自定义。

这时候你就会先百度、google一下,找到如下方案:

  • 1、使用session.createQuery自定义返回Map结果,撸编写一大串代码,jpa就是为了简化代码编写,背道而驰了。
  • 2、在查询的sql中直接使用新建构造对象的: select new top.lingkang.lalanote.vo.FolderVo(e.id,e.name) from FolderEntity e 不优雅,一点也不优雅,你还要维护返回值的构造函数
  • 3、JpaRepository中查询结果使用数组的:List<Object[]> query();,后期维护可能存在变动、也不优雅
  • 4、JpaRepository中查询返回结果是一个接口类的,查询pojo、vo写一堆接口层层转换也不优雅

基于以上种种,我立马分析源码看看怎么配置优雅地返回自定义结果,于是有了这篇文章,包含了我对java的一些理解。花了好几个小时,帮我点点赞吧!

转自:https://lingkang.top/archives/jpa-cha-xun-fan-hui-zi-ding-yi-dui-xiang

1、查询返回某个VO

public interface FolderRepository extends JpaRepository<FolderEntity, String> {@Query("select e from FolderEntity e")public List<FolderVo> get();
}

你要查询某个VO时,这样写会报错:类型转换失败

No converter found capable of converting from type [top.lingkang.lalanote.entity.FolderEntity] to type [top.lingkang.lalanote.vo.FolderVo]

它抛出异常的地方是这里GenericConversionService.handleConverterNotFound,上一级调用是convert
在这里插入图片描述

通过异常栈发现处理返回结果转换的类是ResultProcessor.processResult
再往上也没啥看头了,我打个断点看ResultProcessor.ProjectingConverter.convert
在这里插入图片描述

发现private final ConversionService conversionService;DefaultConversionService默认转换服务,DefaultConversionService默认的转换服务是spring-core所有。

jpa的结果处理:ResultProcessor.ChainingConverter.and 如下

  return intermediate == null || targetType.isInstance(intermediate) ? intermediate: converter.convert(intermediate);

2、源码分析

我们的返回结果是FolderVo,不是表映射实体类FolderEntity 所以targetType.isInstance(intermediate)结果是false,它进入了spring的默认结果转换:DefaultConversionService,其中DefaultConversionService是继承GenericConversionService的。其中的转换处理方法是:

	@Nullableprotected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);GenericConverter converter = this.converterCache.get(key);if (converter != null) {return (converter != NO_MATCH ? converter : null);}// 默认的转换中找不到 FolderEntity 结果转 FolderVoconverter = this.converters.find(sourceType, targetType);if (converter == null) {converter = getDefaultConverter(sourceType, targetType);}if (converter != null) {this.converterCache.put(key, converter);return converter;}this.converterCache.put(key, NO_MATCH);return null;}

我断点找了一两个小时发现jpa的ResultProcessor是不开放配置的,初始化时已经是固定了,底层执行类也是私有的,无法进行直接配置。
那么只能从默认的转换服务下手,即配置GenericConversionService.converters,在converters添加上我们需要的类转换:FolderEntityFolderVo,而且它有对应的添加方法:

private final Converters converters = new Converters();public void addConverter(Converter<?, ?> converter)

看了一遍也没能发现配置GenericConversionService的入口,上面提到过,ResultProcessor数据转换调用的是DefaultConversionService查看了源码发现给我们开放了这个实例:

	/*** Return a shared default {@code ConversionService} instance,* lazily building it once needed.* <p><b>NOTE:</b> We highly recommend constructing individual* {@code ConversionService} instances for customization purposes.* This accessor is only meant as a fallback for code paths which* need simple type coercion but cannot access a longer-lived* {@code ConversionService} instance any other way.* @return the shared {@code ConversionService} instance (never {@code null})* @since 4.3.5*/public static ConversionService getSharedInstance() {DefaultConversionService cs = sharedInstance;if (cs == null) {synchronized (DefaultConversionService.class) {cs = sharedInstance;if (cs == null) {cs = new DefaultConversionService();sharedInstance = cs;}}}return cs;}

注释中说明了这是一个公共共享的转换服务,我们可以直接拿到操作它,往里面添加我们的转换器:
FolderEntityFolderVo

3、定义结果解析,entity转pojo、vo等

首先定义一个转换器:

import cn.hutool.core.bean.BeanUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import top.lingkang.lalanote.entity.FolderEntity;
import top.lingkang.lalanote.vo.FolderVo;import java.util.HashSet;
import java.util.Set;/*** @author lingkang* Created by 2023/8/12*/
@Slf4j
public class EntityToVoGenericConverter implements GenericConverter {// 不必担心性能问题,底层使用了cache存储处理@Overridepublic Set<ConvertiblePair> getConvertibleTypes() {Set<ConvertiblePair> convertiblePairs = new HashSet<>();// 其他转换类可以直接在此添加(这样写是定向)// convertiblePairs.add(new ConvertiblePair(FolderEntity.class, FolderVo.class));// 或者写成这样,这样会匹配所有对象进行转换(推荐)不必担心性能问题,底层使用了cache存储处理convertiblePairs.add(new ConvertiblePair(Object.class, Object.class));return convertiblePairs;}@Overridepublic Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {// System.out.println(source);try {// 直接创建结果对象Object instance = targetType.getType().getDeclaredConstructor().newInstance();// hutool-core中的bean复制:FolderEntity 复制属性到 FolderVoBeanUtil.copyProperties(source, instance);// 返回结果: FolderVoreturn instance;} catch (Exception e) {log.error("无法解析的映射!", e);throw new RuntimeException(e);}}
}

添加一个springboot启动后追加初始化设置

import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.stereotype.Component;/*** @author lingkang* Created by 2023/8/12*/
@Component
@Order(1) //如果多个自定义的 ApplicationRunner  ,用来标明执行的顺序
public class StartRunAfterInit implements ApplicationRunner {@Overridepublic void run(ApplicationArguments args) throws Exception {DefaultConversionService sharedInstance = (DefaultConversionService) DefaultConversionService.getSharedInstance();// 加入我们的解析 FolderEntity → FolderVosharedInstance.addConverter(new EntityToVoGenericConverter());}
}

再执行一次查询,能根据解析返回结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yEEerySS-1691861436064)(/upload/2023/08/image-1691854158520.png)]
在这里插入图片描述

断点也能找到我定义的处理类: EntityToVoGenericConverter
在这里插入图片描述

4、只查询某几个字段

有时候,你不可能把所有的字段都查询出来,只查询其中的两个、或两个以上。(查一个字段可以直接类型返回) 总不能分两次查询、使用数组接收、map接收?这样不优雅,按照上面的配置,我们可以这样:

public interface FolderRepository extends JpaRepository<FolderEntity, String> {@Query("select e from FolderEntity e")public List<FolderVo> get();// 一定要加上 as XXX 否则将无法解析到 AbstractJpaQuery.TupleConverter.TupleBackedMap 中的key值// 像下面的 e.createTime 将无法被解析到@Query("select e.id as id,e.name as name,e.createTime from FolderEntity e")List<FolderVo> getIdAndName();
}

EntityToVoGenericConverter中修改如下

    // 不必担心性能问题,底层使用了cache存储处理@Overridepublic Set<ConvertiblePair> getConvertibleTypes() {Set<ConvertiblePair> convertiblePairs = new HashSet<>();// 其他转换类可以直接在此添加(这样写是定向)// convertiblePairs.add(new ConvertiblePair(FolderEntity.class, FolderVo.class));// 或者写成这样,这样会匹配所有对象进行转换(推荐)convertiblePairs.add(new ConvertiblePair(Object.class, Object.class));// 用于解析 JpaQueryFactory.TupleConverter.TupleBackedMap(这样写是定向)// select e.id as id,e.name as name,e.createTime from FolderEntity econvertiblePairs.add(new ConvertiblePair(Map.class, Object.class));return convertiblePairs;}

执行调用

    @GetMapping("/t2")@ResponseBodypublic Object t2() {return folderRepository.getIdAndName();}

返回结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KcFQOBu5-1691861436065)(/upload/2023/08/image-1691859971475.png)]
可以看到,e.createTime未曾 as createTime导致无法映射到返回的实体类

5、实体类展示

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Data;/*** @author lingkang* created by 2023/7/27*/
@Data
@Entity
@Table(name = "n_folder")
public class FolderEntity extends BaseEntity {@Id@Column(name = "id")private String id;@Column(name = "parent_id")private String parentId;@Column(name = "name")private String name;@Column(name = "attr")private String attr;@Column(name = "type")private Integer type;
}
import lombok.Data;import java.io.Serializable;
import java.util.Date;/*** @author lingkang* created by 2023/8/3*/
@Data
public class FolderVo implements Serializable {private String id;private String parentId;private String name;private String attr;private Integer type;private Date createTime;private Date updateTime;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL的第一篇文章——了解数据库、简单的SQL语句

目录 学习目标 第一章 介绍数据库 1. 数据库概述 2. MySQL概述 第二章 MySQL的使用 1. MySQL服务的启动 2. 客户端连接MySQL 2.1 命令行客户端 第三章 SQL的介绍 1. 什么是SQL 2. SQL的分类 3. MySQL的语法规范和要求 第四章 DDL操作数据库 1. 创建数据库 2. 查…

word横向页面侧面页码设置及转pdf后横线变竖线的解决方案

在处理材料的时候&#xff0c;会遇到同一个文档里自某一页开始&#xff0c;页面布局是横向的&#xff0c;这时候页码要设置在侧面&#xff0c;方法是双击页脚&#xff0c;然后在word工具栏上选择“插入”——>“文本框”——>“绘制竖版文本框”&#xff0c;然后在页面左…

【MFC】10.MFC六大机制:RTTI(运行时类型识别),动态创建机制,窗口切分,子类化-笔记

运行时类信息&#xff08;RTTI&#xff09; C: ##是拼接 #是替换成字符串 // RTTI.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #include <iostream> #include <afxwin.h>#ifdef _DEBUG #define new DEBUG_NEW #endifCWinApp th…

面试笔记:Android 架构岗,一次4小时4面的体验

作者&#xff1a;橘子树 此次面试一共4面4小时&#xff0c;中间只有几分钟间隔。对持续的面试状态考验还是蛮大的。 关于面试的心态&#xff0c;保持悲观的乐观主义心态比较好。面前做面试准备时保持悲观&#xff0c;尽可能的做足准备。面后积极做复盘&#xff0c;乐观的接受最…

明月之刃:armbian巧借nmtui管理网络连接

文章目录 nmtui简介安装nmtuinmtui使用连接wifi设置主机名称 nmtui简介 nmtui是NetworkManager TUI&#xff08;Text User Interface&#xff09;的缩写&#xff0c;它提供了一个可视化的界面来管理网络连接。但是&#xff0c;在Debian系统中&#xff0c;没有默认安装nmtui工具…

Web 服务器 -【Tomcat】的简单学习

Tomcat1 简介1.1 什么是Web服务器 2 基本使用2.1 下载2.2 安装2.3 卸载2.4 启动2.5 关闭2.6 配置2.7 部署 3 Maven创建Web项目3.1 Web项目结构3.2 创建Maven Web项目 4 IDEA使用Tomcat4.1 集成本地Tomcat4.2 Tomcat Maven插件 Tomcat 1 简介 1.1 什么是Web服务器 Web服务器是…

详解JAVA远程debug

目录 1.什么是远程debug&#xff1f; 2.远程debug普通JAVA程序 环境 测试程序 程序启动指令 编译器配置 3.远程debug JAVA Web程序 4.远程debug spring boot程序 1.什么是远程debug&#xff1f; 远程debug&#xff0c;也就是可以在本地debug远端部署的程序&#xff0c…

【数据结构与算法】十大经典排序算法-冒泡排序

&#x1f31f;个人博客&#xff1a;www.hellocode.top &#x1f3f0;Java知识导航&#xff1a;Java-Navigate &#x1f525;CSDN&#xff1a;HelloCode. &#x1f334;掘金&#xff1a;HelloCode &#x1f31e;知乎&#xff1a;HelloCode ⚡如有问题&#xff0c;欢迎指正&#…

MOCK测试

介绍 mock&#xff1a;就是对于一些难以构造的对象&#xff0c;使用虚拟的技术来实现测试的过程。 mock测试&#xff1a;在测试过程中&#xff0c;对于某些不容易构造或者不容易获取的对象&#xff0c;可以用一个虚拟的对象来代替的测试方 法。 接口Mock测试&#xff1a;在接口…

【C++】内存管理与模板

目录 一、内存管理 1.new与delete基本用法 (1) 内置类型 (2) 自定义类型 2.new, delete与malloc, free对比 (1) 内置类型 (2) 自定义类型 (3)综合特点 3.new与delete的底层实现 4. 定位new表达式 二、模板 1.引入机制 2. 基本使用 (1) 函数模板 ①概念&#xff1a…

Hadoop+Python+Django+Mysql热门旅游景点数据分析系统的设计与实现(包含设计报告)

系统阐述的是使用热门旅游景点数据分析系统的设计与实现&#xff0c;对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计&#xff0c;描述&#xff0c;实现和分析与测试方面来表明开发的过程。开发中使用了 django框架和MySql数据库技术搭建系统的整体…

06-1_Qt 5.9 C++开发指南_对话框与多窗体设计_标准对话框

在一个完整的应用程序设计中&#xff0c;不可避免地会涉及多个窗体、对话框的设计和调用&#xff0c;如何设计和调用这些对话框和窗体是搞清楚一个庞大的应用程序设计的基础。本章将介绍对话框和多窗体设计、调用方式、数据传递等问题&#xff0c;主要包括以下几点。 Qt 提供的…

OffSec Labs Proving grounds Play——FunboxEasyEnum

文章目录 端口扫描目录扫描文件上传漏洞利用查看用户爆破密码sudo提权flag位置FunboxEasyEnum writeup walkthrough Funbox: EasyEnum ~ VulnHub Enumeration Brute-force the web server’s files and directories. Be sure to check for common file extensions. Remote…

Hadoop理论及实践-HDFS四大组件关系(参考Hadoop官网)

NameNode&#xff08;名称节点&#xff0c;Master主节点&#xff09; NameNode主要功能 1、NameNode负责管理HDFS文件系统的元数据&#xff0c;包括文件&#xff0c;目录&#xff0c;块信息等。它将元数据Fsimage与Edit_log持久化到硬盘上。一个是Fsimage(镜像文件&#xff09…

android,Compose,消息列表和动画(点击item的时候,就会删除)

Compose,消息列表和动画&#xff08;点击item的时候&#xff0c;就会删除&#xff09; package com.example.mycompose08import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundat…

PoseiSwap 开启“Poseidon”池,治理体系或将全面开启

PoseiSwap 曾在前不久分别以 IDO、IEO 的方式推出了 POSE 通证&#xff0c;但 PoseiSwap DEX 中并未向除 Zepoch 节点外的角色开放 POSE 资产的交易。而在前不久&#xff0c;PoseiSwap 推出了全新的“Poseidon”池&#xff0c;该池将向所有用户开放&#xff0c;并允许用户自由的…

Git:在本地电脑上如何使用git?

git 版本&#xff1a; 2.40.1.windows.1 文章目录 一. 使用git之前你必须要理解的几个概念1.1 理解工作区、版本库、暂存区的概念1.2 提交Git版本库的步骤【分两步执行】 二. Git本地库实战2.1 初始化版本库2.2 新建 & 提交 & 状态2.3 查看日志2.4 回退 & 穿梭 &am…

Codeforces Round 892 (Div. 2) C. Another Permutation Problem 纯数学方法 思维题

Codeforces Round 892 (Div. 2) C. Another Permutation Problem 源码&#xff1a; #include <iostream> #include <algorithm> #include <set> #include <map> #include <queue> #include <vector> #include <stack> #include &l…

面试热题(螺旋矩阵)

给你一个 m 行 n 列的矩阵 matrix &#xff0c;请按照 顺时针螺旋顺序 &#xff0c;返回矩阵中的所有元素 一看到这个大家有没有想到 就是一个螺旋形状&#xff0c;那这道题我们应该怎么解决&#xff1f; 我们先来仔细的看&#xff0c;它这种螺旋形状的遍历是先【右-下-左-上】…

aardio 调用 python pickle load 数据

aardio 调用 python pickle load 词典数据&#xff1b; pip install readmdict dump_pickle.py import os import sys import time import pickle from readmdict import MDX, MDDos.chdir("/mdict")mdxfile "your.mdx" if not os.path.exists(mdxfil…