你懂Jpa自定义原生sql语句的查询结果如何转化为对象吗?

在使用Spring Data Jpa框架时,根据业务需求我们通常需要进行复杂的数据库查询,并返回我们自定义的实体类,而在该框架下,目前仅仅支持返回与数据库映射进行持久化的POJO实体。虽然在框架上我们可以使用@Query注解执行我们自定义的sql语句,但是其返回值为List<Object[]> 类型,即多个Object数组的List集合。

下面我们介绍一下关于在Spring Data Jpa框架下使用自定义查询语句返回自定义实体的解决方案。

解决方案一:

例如我们有如下相关联实体:

User实体
@Entity
@Getter
@Setter
@Table(name="tab_user")
public class User extends BaseEntity implements Serializable {
 
    @Id
    @NotNull(groups = Update.class)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "user_id")
    @ApiModelProperty(value = "主键")
    private Long userId;
 
    @Column(name = "username",nullable = false)
    @NotBlank
    @ApiModelProperty(value = "姓名")
    private String username;
  
 
    @Column(name = "branch_id",nullable = false)
    @NotNull
    @ApiModelProperty(value = "机构id")
    private Long branchId;
 
    @Column(name = "job_id",nullable = false)
    @NotNull
    @ApiModelProperty(value = "岗位id")
    private Long jobId;
 
 
    @Override
    public int hashCode() {
        return Objects.hash(id, username);
    }
}
Job实体
package com.sgcc.modules.system.domain;
 
import com.sgcc.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
 
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.util.Objects;
 
/**
 *  @author: liman
 *  @Date: 2020/7/17 17:37
 *  @Description: 员工表
 */
 
@Entity
@Getter
@Setter
@Table(name="tab_job")
public class JobTab extends BaseEntity implements Serializable {
 
    @Id
    @Column(name = "job_id")
    @NotNull(groups = Update.class)
    @ApiModelProperty(value = "ID", hidden = true)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long jobId;
 
    @Column(name = "job_name", nullable = false)
    @NotBlank
    @ApiModelProperty(value = "岗位名称")
    private String jobName;
 
    @Column(name = "branch_id", nullable = false)
    @NotBlank
    @ApiModelProperty(value = "岗位机构")
    private String branchId;
 
    @Column(name = "job_type", nullable = false)
    @NotBlank
    @ApiModelProperty(value = "岗位类别")
    private String jobType;
 
 
  
}
Branch实体
package com.sgcc.modules.system.domain;
 
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.sgcc.base.BaseEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
 
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
 
/**
 * @website https://el-admin.vip
 * @description /
 * @author liman
 * @date 2020-07-07
 **/
@Entity
@Getter
@Setter
@Table(name="tab_branch")
public class Branch extends BaseEntity implements Serializable {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    @ApiModelProperty(value = "id")
    private Long id;
 
    @Column(name = "branch_id",nullable = false)
    @NotNull
    @ApiModelProperty(value = "机构id")
    private Long branchId;
 
    @Column(name = "branch_name",nullable = false)
    @NotBlank
    @ApiModelProperty(value = "机构名称")
    private String branchName;
 
    @Column(name = "level",nullable = false)
    @NotNull
    @ApiModelProperty(value = "级别")
    private Integer level;
 
    @Column(name = "p_id",nullable = false)
    @NotNull
    @ApiModelProperty(value = "父级机构id")
    private Long pId;
 
    @Column(name = "dept_sort",nullable = false)
    @NotNull
    @ApiModelProperty(value = "部门排序级别")
    private Integer deptSort;
 
 
    }
}

要转化成的实体Dto如下:

package com.sgcc.modules.system.domain.resp;
 
import lombok.Getter;
import lombok.Setter;
 
/**
 *  @author: liman
 *  @Date: 2020/7/17 16:42
 *  @Description: 测评对象返回类
 */
 
@Getter
@Setter
 
public class EvaUserResp {
    /** 所在部门*/
    private String branchName;
    /** 测评对象*/
    private String userName;
    /** 职位*/
    private String jobName;
 
    /** 职位类别*/
    private String jobType;
 
    public EvaUserResp() {
    }
 
    public EvaUserResp(String branchName, String userName, String jobName, String jobType) {
        this.branchName = branchName;
        this.userName = userName;
        this.jobName = jobName;
        this.jobType = jobType;
    }
}

下面我们来看下DAO层的JPA处理接口类

public interface BookInfoRepository extends JpaRepository<BookInfo, BigDecimal> {
 
        @Query(value = "SELECT b.branch_name as branchName," +
            "a.username as userName," +
            "c.job_name as jobName," +
            "c.job_type as jobType FROM tab_user a " +
            "INNER JOIN tab_branch b ON b.branch_id = a.branch_id " +
            "INNER JOIN tab_job c on c.job_id = a.job_id " +
            "WHERE  user_id = :userId ", nativeQuery = true)
    List<Object[]>findByUserId(@Param("userId") Long userId);
}

我们来解释一下上面这个处理接口类中的要点:

①nativeQuery=true,属性的设置,是表明该方法中的sql以数据库的sql语句格式对待。

②返回值为List<Object[]>,由于我们之前说过Jpa无法自动完成查询结果到自定义实体的映射,所以我们要使用该对象接收。

最后我们看下将该List<Object[]>对象转换为我们自定义实体的工具类:

public class EntityUtils {
    private static Logger logger = LoggerFactory.getLogger(EntityUtils.class);
 
    /**
     * 将数组数据转换为实体类
     * 此处数组元素的顺序必须与实体类构造函数中的属性顺序一致
     *
     * @param list           数组对象集合
     * @param clazz          实体类
     * @param <T>            实体类
     * @param model          实例化的实体类
     * @return 实体类集合
     */
    public static <T> List<T> castEntity(List<Object[]> list, Class<T> clazz, Object model) {
        List<T> returnList = new ArrayList<T>();
        if (list.isEmpty()) {
            return returnList;
        }
        //获取每个数组集合的元素个数
        Object[] co = list.get(0);
 
        //获取当前实体类的属性名、属性值、属性类别
        List<Map> attributeInfoList = getFiledsInfo(model);
        //创建属性类别数组
        Class[] c2 = new Class[attributeInfoList.size()];
        //如果数组集合元素个数与实体类属性个数不一致则发生错误
        if (attributeInfoList.size() != co.length) {
            return returnList;
        }
        //确定构造方法
        for (int i = 0; i < attributeInfoList.size(); i++) {
            c2[i] = (Class) attributeInfoList.get(i).get("type");
        }
        try {
            for (Object[] o : list) {
                Constructor<T> constructor = clazz.getConstructor(c2);
                returnList.add(constructor.newInstance(o));
            }
        } catch (Exception ex) {
            logger.error("实体数据转化为实体类发生异常:异常信息:{}", ex.getMessage());
            return returnList;
        }
        return returnList;
    }
 
    /**
     * 根据属性名获取属性值
     *
     * @param fieldName 属性名
     * @param modle     实体类
     * @return 属性值
     */
    private static Object getFieldValueByName(String fieldName, Object modle) {
        try {
            String firstLetter = fieldName.substring(0, 1).toUpperCase();
            String getter = "get" + firstLetter + fieldName.substring(1);
            Method method = modle.getClass().getMethod(getter, new Class[]{});
            Object value = method.invoke(modle, new Object[]{});
            return value;
        } catch (Exception e) {
            return null;
        }
    }
 
    /**
     * 获取属性类型(type),属性名(name),属性值(value)的map组成的list
     *
     * @param model 实体类
     * @return list集合
     */
    private static List<Map> getFiledsInfo(Object model) {
        Field[] fields = model.getClass().getDeclaredFields();
        List<Map> list = new ArrayList(fields.length);
        Map infoMap = null;
        for (int i = 0; i < fields.length; i++) {
            infoMap = new HashMap(3);
            infoMap.put("type", fields[i].getType());
            infoMap.put("name", fields[i].getName());
            infoMap.put("value", getFieldValueByName(fields[i].getName(), model));
            list.add(infoMap);
        }
        return list;
    }
}

在执行DAO层方法,获得相应的List<Object[]>对象调用工具类中的静态方法castEntity,即可将数据转换为自定义实体。

在使用该解决方案时,需注意以下几点要求:

①自定义查询语句中的查询字段的顺序一定要和自定义实体的构造方法中的属性顺序一致。

②此种方案在解决特别复杂的查询语句时很高效,因为只需自定义查询语句,与数据库进行一次交互即可,效率可观。但对程序的规范性要求比较高。

③此方案在解决当前项目数据库中数据表在业务需求下创建而不符合使用JPA框架创建持久化实体之间的关联关系(即因为业务需求,所建立库表不符合数据库建库规范),而又需要进行多表关联进行复杂查询时,很实用。

特别说明:上面所举的例子只是单纯为了演示此方案,因为上面表关联之简单要获得如上的结果使用JPA框架也可轻松实现。

具体转化demo如下:

 /**object对象转化EvaUserResp对象*/
 List<Object[]> evaUserResp = userRepository.findByUserId(x.getUserId());
 List<EvaUserResp> evaUserResps = EntityUtils.castEntity(evaUserResp, EvaUserResp.class, new EvaUserResp());

解决方案二:

修改DAO层的JPA处理接口类:

@Query(value = "SELECT new EvaUserResp(b.branch_name as branchName,a.username as userName,c.job_name as jobName,c.job_type as jobType ) FROM tab_user a INNER JOIN tab_branch b ON b.branch_id = a.branch_id INNER JOIN tab_job c on c.job_id = a.job_id WHERE user_id = 4 ", nativeQuery = true)
List<EvaUserResp> findByUserId(@Param("userId") Long userId);

注意:这次的EvaUserResp最好写全路径,程序有可能无法定位到该类

解决方案三:

我要解决这样一条sql查询出来的结果:

SELECT b.branch_name as branchName," +
            "a.username as userName," +
            "c.job_name as jobName," +
            "c.job_type as jobType FROM tab_user a " +
            "INNER JOIN tab_branch b ON b.branch_id = a.branch_id " +
            "INNER JOIN tab_job c on c.job_id = a.job_id " +
            "WHERE  user_id = :userId 

第一步,首先要引入阿里的fastJson

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.56</version>

</dependency>

第二步:Repository层用List<Map<String,String>>接收返回的数据

 @Query(value = "SELECT b.branch_name as branchName," +
            "a.username as userName," +
            "c.job_name as jobName," +
            "c.job_type as jobType FROM tab_user a " +
            "INNER JOIN tab_branch b ON b.branch_id = a.branch_id " +
            "INNER JOIN tab_job c on c.job_id = a.job_id " +
            "WHERE  user_id = :userId ", nativeQuery = true)
    List<Map<String,String>>  findByUserId(@Param("userId") Long userId);

第三步进行转化:

    List<Map<String,String>> evaUserResp = userRepository.findByUserId(x.getUserId());
    String irsStr = JSON.toJSONString(evaUserResp);
    List<EvaUserResp> evaUserResps = JSON.parseArray(irsStr,EvaUserResp.class);

感谢你看到这里,我是程序员麦冬,一个java开发从业者,深耕行业六年了,每天都会分享java相关技术文章或行业资讯

欢迎大家关注和转发文章,后期还有福利赠送!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,692评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,482评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,995评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,223评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,245评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,208评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,091评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,929评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,346评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,570评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,739评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,437评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,037评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,677评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,833评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,760评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,647评论 2 354