JPA如何将查询结果转换为DTO对象

目录
  • 前言
  • 例子
    • mysql数据库表
    • 联合查询的需求
    • sql语句
    • 如何在JPA中映射为DTO对象
  • 例子涉及的部分源代码

前言

JPA支持使用@Query自定义查询,查询的结果需要字节用DTO对象接收,如果使用HQL的查询语句,可以将直接将DTO对象的构造方法传入hql中,直接转为DTO对象;而如果使用native sql查询的方式,只能将返回结果用Object[]对象接收,然后DTO设置对象的构造来接收Object[]里面的参数完成DTO对象的转换。

例子

mysql数据库表

用户表

CREATE TABLE `pos_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `user_pwd` varchar(255) DEFAULT NULL,
  `user_type` int(11) DEFAULT NULL,
  `parent_id` bigint(20) DEFAULT NULL,
  `user_status` int(11) DEFAULT NULL,
  `distributor_id` bigint(20) DEFAULT NULL,
  `creator_identity_type` int(2) DEFAULT NULL,
  `creator_id` bigint(20) DEFAULT NULL,
  `create_date` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;

设备表

CREATE TABLE `pos_device` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `imei` varchar(120) NOT NULL,
  `mac` varchar(120) NOT NULL,
  `unique_code` varchar(120) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  `type` varchar(100) DEFAULT NULL,
  `system_version` varchar(100) DEFAULT NULL,
  `distributor_id` bigint(20) DEFAULT NULL,
  `creator_identity_type` int(2) DEFAULT NULL,
  `creator_id` bigint(20) DEFAULT NULL,
  `create_date` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8;

用户和设备关联表

CREATE TABLE `pos_user_device_relation` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `device_id` bigint(20) DEFAULT NULL,
  `user_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

可以看到用户和设备关联表中有用户id和设备id

联合查询的需求

想列出pos_user_device_relation表中所有pos_user的distributor_id=1的所有用户和设备,要求返回的信息包括用户的username、type信息和设备的imei、mac等信息。

sql语句

SELECT
pdr.id,
pdr.device_id,
pd.imei,
pd.mac,
pd.unique_code,
pd.type,
pd.system_version,
pdr.user_id,
pu.user_name,
pu.user_type
FROM
pos_user_device_relation pdr, pos_user pu, pos_device pd
WHERE pdr.device_id = pd.id AND pdr.user_id = pu.id AND pdr.user_id in (select id from pos_user where distributor_id=1) limit 0,10

查询可以正常得到结果,结果行是这样的:

+----+-----------+---------------------+-------------------+--------------------------+----------+----------------+---------+---------------+-----------+
| id | device_id | imei                | mac               | unique_code              | type     | system_version | user_id | user_name     | user_type |
+----+-----------+---------------------+-------------------+--------------------------+----------+----------------+---------+---------------+-----------+

如何在JPA中映射为DTO对象

DTO对象字段定义如下:

private Long posUserDeviceId;
private Long deviceId;
private String deviceImei;
private String deviceMac;
private String deviceUniqueCode;
private String deviceType;
private String deviceSystemVersion;
private Long userId;
private String username;
private PosUserEntityConstants.UserType userType;

对象中的PosUserEntityConstants.UserType是一个自定义转换类型,通过继承AttributeConverter将Integer转换为UserType的枚举。

方法一:使用HQL的方法

Repository的查询代码如下:

@Query(
        value = "SELECT\n" +
                "new com.hengbao.ethiopiatelecomrecharge.dao.dto.PosUserDeviceRelationDto(\n" +
                "pdr.id,\n" +
                "pdr.deviceId,\n" +
                "pd.imei,\n" +
                "pd.mac,\n" +
                "pd.uniqueCode,\n" +
                "pd.type,\n" +
                "pd.systemVersion,\n" +
                "pdr.userId,\n" +
                "pu.userName,\n" +
                "pu.userType\n" +
                ") \n" +
                "FROM \n" +
                "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd \n" +
                "WHERE pdr.deviceId = pd.id AND pdr.userId = pu.id AND pdr.userId in (select id from PosUserEntity where distributorId=?1)",
        countQuery = "SELECT count(*) FROM \n" +
                "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd \n" +
                "WHERE pdr.deviceId = pd.id AND pdr.userId = pu.id AND pdr.userId in (select id from PosUserEntity where distributorId=?1)"
)
Page<PosUserDeviceRelationDto> findUserAndDeviceInfoByDistributorId(Long distributorId, Pageable pageable);

可以看到HQL的方法将PosUserDeviceRelationDto的构造器直接传入到HQL语句中,省去了我们自行转换的麻烦。那么PosUserDeviceRelationDto中也要重写一个相应的构造器:

由于项目中使用了lombok,所有最终dto的代码只是在类上面加上了一些注解,@AllArgsConstructor的注解会自动生成一个全参数的构造器,构造器的顺序和字段定义顺序一致,类代码如下:

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class PosUserDeviceRelationDto implements Serializable {
    /**
     * 版本号
     */
    private static final long serialVersionUID = 1L; 
    private Long posUserDeviceId; 
    private Long deviceId;
    private String deviceImei;
    private String deviceMac;
    private String deviceUniqueCode;
    private String deviceType;
    private String deviceSystemVersion; 
    private Long userId;
    private String username;
    private PosUserEntityConstants.UserType userType;
}

方法二:使用native query的方式查询并转换为dto

Repository的查询代码如下:

@Query(
        value = "SELECT\n" +
                "pdr.id,\n" +
                "pdr.device_id,\n" +
                "pd.imei,\n" +
                "pd.mac,\n" +
                "pd.unique_code,\n" +
                "pd.type,\n" +
                "pd.system_version,\n" +
                "pdr.user_id,\n" +
                "pu.user_name,\n" +
                "pu.user_type\n" +
                "FROM\n" +
                "pos_user_device_relation pdr, pos_user pu, pos_device pd\n" +
                "WHERE pdr.device_id = pd.id AND pdr.user_id = pu.id AND pdr.user_id in (select id from pos_user where distributor_id=?1)",
        countQuery = "SELECT count(*) FROM\n" +
                "pos_user_device_relation pdr, pos_user pu, pos_device pd\n" +
                "WHERE pdr.device_id = pd.id AND pdr.user_id = pu.id AND pdr.user_id in (select id from pos_user where distributor_id=?1)",
        nativeQuery = true
)
Page<Object[]> findUserAndDeviceInfoByDistributorId2(Long distributorId, Pageable pageable);

可以看到这样只能用Object[]来接收结果集,而不能直接将返回参数定义为PosUserDeviceRelationDto对象,否则会报no converter的异常。

那如何将Object[]的结果集转换为PosUserDeviceRelationDto对象呢?

首先先看一下Object[]每个对象的类型:BigInteger BigInteger String String String String String BigInteger String Integer

这是可以发现虽然mysql数据库定义的是bigint(20)类型,但是结果集是BigInteger,不能直接用Long接收,所以专门定义一个dto的构造器如下:

public PosUserDeviceRelationDto(BigInteger posUserDeviceId,
                                BigInteger deviceId,
                                String deviceImei,
                                String deviceMac,
                                String deviceUniqueCode,
                                String deviceType,
                                String deviceSystemVersion,
                                BigInteger userId,
                                String username,
                                Integer userType) {
    this.posUserDeviceId = posUserDeviceId == null ? null : posUserDeviceId.longValue();
    this.deviceId = deviceId == null ? null : deviceId.longValue();
    this.deviceImei = deviceImei;
    this.deviceMac = deviceMac;
    this.deviceUniqueCode = deviceUniqueCode;
    this.deviceType = deviceType;
    this.deviceSystemVersion = deviceSystemVersion;
    this.userId = userId == null ? null : userId.longValue();
    this.username = username;
    // UserTypeConverter是继承自javax.persistence.AttributeConverter的类型转换器
    this.userType = new PosUserEntityConstants.UserTypeConverter().convertToEntityAttribute(userType);
}

然后直接调用构造即可:

Page<Object[]> userAndDeviceInfoByDistributorId2 = posUserDeviceRelationRepository.findUserAndDeviceInfoByDistributorId2(1L, PageRequest.of(0, 10));
for (Object[] objects : userAndDeviceInfoByDistributorId2.getContent()) {
    // 转换成dto的方法一:将objects中的所有参数强转为对应类型,传递到dto的构造器中;dto对象定义好对应的构造器
    PosUserDeviceRelationDto dto1 = new PosUserDeviceRelationDto(
            (BigInteger) objects[0],
            (BigInteger) objects[1],
            (String    ) objects[2],
            (String    ) objects[3],
            (String    ) objects[4],
            (String    ) objects[5],
            (String    ) objects[6],
            (BigInteger) objects[7],
            (String    ) objects[8],
            (Integer   ) objects[9]);
    System.out.println(dto1);

网上还能搜到另外一种解决方法,就是通过反射的方法简化dto的转化步骤(https://www.jb51.net/article/238470.htm),但是这个存在bug,如果返回的objects数组中有一个值为null,那么getClass()方法获取类的类型就会报错,所以改为将每个参数的类型直接传入进去,可以这样使用反射其实省不了多少工夫了:

Page<Object[]> userAndDeviceInfoByDistributorId2 = posUserDeviceRelationRepository.findUserAndDeviceInfoByDistributorId2(1L, PageRequest.of(0, 10));
for (Object[] objects : userAndDeviceInfoByDistributorId2.getContent()) {
    // 转换成dto的方法二:反射的方法直接调用构造
    PosUserDeviceRelationDto dto2 = caseDto(objects, new Class[]{BigInteger.class,
                    BigInteger.class,
                    String.class,
                    String.class,
                    String.class,
                    String.class,
                    String.class,
                    BigInteger.class,
                    String.class,
                    Integer.class},
            PosUserDeviceRelationDto.class);
    System.out.println(dto2);
}
/**
 * 网页中直接使用objectArray中获取每一个class,但是这样有一个问题,就是如果获取的objectArray中有一个空值的话,不能获取到class,
 * 导致不能获取到对象的构造器
 * @param objectArray
 * @param objectClassArray
 * @param dtoClass
 * @param <T>
 * @return
 */
private <T> T caseDto(Object[] objectArray, Class[] objectClassArray, Class<T> dtoClass) throws Exception {
    Constructor<T> constructor = dtoClass.getConstructor(objectClassArray);
    return constructor.newInstance(objectArray);
}

例子涉及的部分源代码

Repository

@Query(
        value = "SELECT\n" +
                "new com.hengbao.ethiopiatelecomrecharge.dao.dto.PosUserDeviceRelationDto(\n" +
                "pdr.id,\n" +
                "pdr.deviceId,\n" +
                "pd.imei,\n" +
                "pd.mac,\n" +
                "pd.uniqueCode,\n" +
                "pd.type,\n" +
                "pd.systemVersion,\n" +
                "pdr.userId,\n" +
                "pu.userName,\n" +
                "pu.userType\n" +
                ") \n" +
                "FROM \n" +
                "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd \n" +
                "WHERE pdr.deviceId = pd.id AND pdr.userId = pu.id AND pdr.userId in (select id from PosUserEntity where distributorId=?1)",
        countQuery = "SELECT count(*) FROM \n" +
                "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd \n" +
                "WHERE pdr.deviceId = pd.id AND pdr.userId = pu.id AND pdr.userId in (select id from PosUserEntity where distributorId=?1)"
)
Page<PosUserDeviceRelationDto> findUserAndDeviceInfoByDistributorId(Long distributorId, Pageable pageable);
@Query(
        value = "SELECT\n" +
                "pdr.id,\n" +
                "pdr.device_id,\n" +
                "pd.imei,\n" +
                "pd.mac,\n" +
                "pd.unique_code,\n" +
                "pd.type,\n" +
                "pd.system_version,\n" +
                "pdr.user_id,\n" +
                "pu.user_name,\n" +
                "pu.user_type\n" +
                "FROM\n" +
                "pos_user_device_relation pdr, pos_user pu, pos_device pd\n" +
                "WHERE pdr.device_id = pd.id AND pdr.user_id = pu.id AND pdr.user_id in (select id from pos_user where distributor_id=?1)",
        countQuery = "SELECT count(*) FROM\n" +
                "pos_user_device_relation pdr, pos_user pu, pos_device pd\n" +
                "WHERE pdr.device_id = pd.id AND pdr.user_id = pu.id AND pdr.user_id in (select id from pos_user where distributor_id=?1)",
        nativeQuery = true
)
Page<Object[]> findUserAndDeviceInfoByDistributorId2(Long distributorId, Pageable pageable);

DTO类

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class PosUserDeviceRelationDto implements Serializable {
    /**
     * 版本号
     */
    private static final long serialVersionUID = 1L;
    private Long posUserDeviceId;
    private Long deviceId;
    private String deviceImei;
    private String deviceMac;
    private String deviceUniqueCode;
    private String deviceType;
    private String deviceSystemVersion;
    private Long userId;
    private String username;
    private PosUserEntityConstants.UserType userType;
    public PosUserDeviceRelationDto(BigInteger posUserDeviceId,
                                    BigInteger deviceId,
                                    String deviceImei,
                                    String deviceMac,
                                    String deviceUniqueCode,
                                    String deviceType,
                                    String deviceSystemVersion,
                                    BigInteger userId,
                                    String username,
                                    Integer userType) {
        this.posUserDeviceId = posUserDeviceId == null ? null : posUserDeviceId.longValue();
        this.deviceId = deviceId == null ? null : deviceId.longValue();
        this.deviceImei = deviceImei;
        this.deviceMac = deviceMac;
        this.deviceUniqueCode = deviceUniqueCode;
        this.deviceType = deviceType;
        this.deviceSystemVersion = deviceSystemVersion;
        this.userId = userId == null ? null : userId.longValue();
        this.username = username;
        // UserTypeConverter是继承自javax.persistence.AttributeConverter的类型转换器
        this.userType = new PosUserEntityConstants.UserTypeConverter().convertToEntityAttribute(userType);
    }
}

test测试类:

@Test
public void testFindUserAndDeviceInfoByDistributorId() throws Exception {
    System.out.println("-----------------hql query-----------------");
    Page<PosUserDeviceRelationDto> userAndDeviceInfoByDistributorId = posUserDeviceRelationRepository.findUserAndDeviceInfoByDistributorId(1L, PageRequest.of(0, 10));
    System.out.println("count=" + userAndDeviceInfoByDistributorId.getTotalElements());
    if(userAndDeviceInfoByDistributorId.getContent() != null) {
        for (PosUserDeviceRelationDto dto : userAndDeviceInfoByDistributorId.getContent()) {
            System.out.println(dto);
        }
    }

    System.out.println("-----------------native sql query-----------------");
    Page<Object[]> userAndDeviceInfoByDistributorId2 = posUserDeviceRelationRepository.findUserAndDeviceInfoByDistributorId2(1L, PageRequest.of(0, 10));
    System.out.println("count=" + userAndDeviceInfoByDistributorId2.getTotalElements());
    if(userAndDeviceInfoByDistributorId2.getContent() != null) {
        for (Object[] objects : userAndDeviceInfoByDistributorId2.getContent()) {
            for (Object obj : objects) {
                System.out.print(obj + "(" + (obj == null ? null : obj.getClass().getSimpleName()) + ") ");
            }
            System.out.println();
        }

        // 转换为dto 方法一
        System.out.println("-----转换dto的第一种方法-----");
        for (Object[] objects : userAndDeviceInfoByDistributorId2.getContent()) {
            // 转换成dto的方法一:将objects中的所有参数强转为对应类型,传递到dto的构造器中;dto对象定义好对应的构造器
            PosUserDeviceRelationDto dto1 = new PosUserDeviceRelationDto(
                    (BigInteger) objects[0],
                    (BigInteger) objects[1],
                    (String    ) objects[2],
                    (String    ) objects[3],
                    (String    ) objects[4],
                    (String    ) objects[5],
                    (String    ) objects[6],
                    (BigInteger) objects[7],
                    (String    ) objects[8],
                    (Integer   ) objects[9]);
            System.out.println(dto1);
        }

        // 转换为dto 方法二
        System.out.println("-----转换dto的第二种方法-----");
        for (Object[] objects : userAndDeviceInfoByDistributorId2.getContent()) {
            // 转换成dto的方法二:反射的方法直接调用构造
            PosUserDeviceRelationDto dto2 = caseDto(objects, new Class[]{BigInteger.class,
                            BigInteger.class,
                            String.class,
                            String.class,
                            String.class,
                            String.class,
                            String.class,
                            BigInteger.class,
                            String.class,
                            Integer.class},
                    PosUserDeviceRelationDto.class);
            System.out.println(dto2);
        }
    }
}

/**
 * 网页中直接使用objectArray中获取每一个class,但是这样有一个问题,就是如果获取的objectArray中有一个空值的话,不能获取到class,
 * 导致不能获取到对象的构造器
 * @param objectArray
 * @param objectClassArray
 * @param dtoClass
 * @param <T>
 * @return
 */
private <T> T caseDto(Object[] objectArray, Class[] objectClassArray, Class<T> dtoClass) throws Exception {
    Constructor<T> constructor = dtoClass.getConstructor(objectClassArray);
    return constructor.newInstance(objectArray);
}

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • JPA 查询原生SQL转换VO对象方式

    目录 JPA查询原生SQL转换VO对象 JPA非注解方式原生sql查询的一些注意事项 JPA查询原生SQL转换VO对象 List<String> sqlList = RiverCourseSql.getRiverCourseSQLString(new RiverCourseDataQO()); List<RiverCourseDataVO> riverCourseDataVO = rsvrfsrBDao.executeNativeQuery2Obj(sqlList.get(0),

  • Spring Data Jpa实现自定义repository转DTO

    近期项目中需要 关联 几张表再把字段转出来,在这里记录以下,我感觉网上写的都不太规范和清晰. @Entity @SqlResultSetMapping( name="TestMapping", entities = { @EntityResult( entityClass = com.xxx.xx.data.model.TestEntity.class, fields = { @FieldResult(name="id",column="id")

  • spring data jpa 查询自定义字段,转换为自定义实体方式

    目标:查询数据库中的字段,然后转换成 JSON 格式的数据,返回前台. 环境:idea 2016.3.4, jdk 1.8, mysql 5.6, spring-boot 1.5.2 背景:首先建立 entity 映射数据库(非专业 java 不知道这怎么说) @Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long

  • Spring Data JPA 映射VO/DTO对象方式

    目录 Spring Data JPA 映射VO/DTO对象 HQL方式 原生SQL的形式 Spring Data Jpa 自定义repository转DTO Spring Data JPA 映射VO/DTO对象 在项目开发中,时常需要根据业务需求来映射VO/DTO对象(这两个概念理解感觉很模糊- .- ),本文将简单介绍以Spring Data JPA的方式处理实体类映射 HQL方式 public interface MusicTypeRepository extends JpaReposito

  • JPA如何将查询结果转换为DTO对象

    目录 前言 例子 mysql数据库表 联合查询的需求 sql语句 如何在JPA中映射为DTO对象 例子涉及的部分源代码 前言 JPA支持使用@Query自定义查询,查询的结果需要字节用DTO对象接收,如果使用HQL的查询语句,可以将直接将DTO对象的构造方法传入hql中,直接转为DTO对象:而如果使用native sql查询的方式,只能将返回结果用Object[]对象接收,然后DTO设置对象的构造来接收Object[]里面的参数完成DTO对象的转换. 例子 mysql数据库表 用户表 CREAT

  • 让JPA的Query查询接口返回Map对象的方法

    在JPA 2.0 中我们可以使用entityManager.createNativeQuery()来执行原生的SQL语句. 但当我们查询结果没有对应实体类时,query.getResultList()返回的是一个List<Object[]>.也就是说每行的数据被作为一个对象数组返回. 常见的用法是这样的: public void testNativeQuery(){ Query query = entityManager.createNativeQuery("select id, n

  • PHP将MySQL的查询结果转换为数组并用where拼接的示例

    mysql查询结果转换为PHP数组的几种方法的区别:  $result = mysql_fetch_row():这个函数返回的是数组,数组是以数字作为下标的,你只能通过$result[0],$Result[2]这样的形式来引用. $result = mysql_fetch_assoc():这个函数返回是以字段名为下标的数组,只能通过字段名来引用.$result['field1']. $result = mysql_fetch_array():这个函数返回的是一个混合的数组,既可以通过数字下标来引

  • JPA CriteriaBuilder子查询方式

    目录 JPA CriteriaBuilder子查询 Jpa在CriteriaBuilder中添加where条件NotIn子查询 JPA CriteriaBuilder子查询 给自己做个备忘 /** 检索关键字 */ if (keyWord != null && !keyWord.trim().equals("")) { // 创建子查询对象 Subquery<Store> subquery = criteriaQuery.subquery(Store.cla

  • jpa EntityManager 复杂查询实例

    目录 jpa EntityManager复杂查询 概念 1. 注入entitymanager 2. 建立连接,执行查询操作 拓展 基于EntityManager的使用 1.最基础的查询 2.spring data jpa的toPredicate方法 jpa EntityManager复杂查询 概念 EntityManager:EntityManager是JPA中用于增删改查的接口,它的作用相当于一座桥梁,连接内存中的java对象和数据库的数据存储.可以用getCriteriaBuilder()的

  • JPA CriteriaBuilder子查询方式

    目录 JPA CriteriaBuilder子查询 Jpa 在CriteriaBuilder中添加where条件NotIn子查询 JPA CriteriaBuilder子查询 给自己做个备忘 /** 检索关键字 */ if (keyWord != null && !keyWord.trim().equals("")) { // 创建子查询对象 Subquery<Store> subquery = criteriaQuery.subquery(Store.cl

  • Spring JPA联表查询之OneToOne源码详解

    目录 前言 源码 注解属性 单向联表 user 实体类 car 实体类 查询结果 双向联表 user 实体 car 实体 查询结果 延迟加载(懒加载) user 实体 查询结果: 查询完会发现,控制台又打印了一个 JPQL: 最后结论 前言 前面几篇我们学习的都是单表查询,就是对一张表中的数据进行查询.而实际项目中,基本都会有多张表联合查询的情况,今天我们就来了解下JPA的联表查询是如做的. 源码 @OneToOne 注解实现一对一关系映射.比如用户跟车辆的关系(这里假设一个人只能有一辆车),一

  • js 将json字符串转换为json对象的方法解析

    例如: JSON字符串: var str1 = '{ "name": "cxh", "sex": "man" }'; JSON对象: var str2 = { "name": "cxh", "sex": "man" }; 一.JSON字符串转换为JSON对象 要使用上面的str1,必须使用下面的方法先转化为JSON对象: //由JSON字符串转换为

  • JS操作xml对象转换为Json对象示例

    最近在处理一个前端功能的时候,遇到了一个问题,原本的系统是使用的xml来存储数据的,而在新的系统上,想要转换为json对象存储,于是就考虑到直接将xml对象转换为json对象. 目前为止,通用的转换方式我还没有找到,只有根据已知的xml对象结构来使用for循环不断地赋值. var xml = ······;//获取xml对象 var objList = []; for(var i = 0;i<xml.childNodes.length;i++){ var obj = {}; obj.attrib

随机推荐