前言

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

例子

mysql数据库表

用户表

  1. CREATE TABLE `pos_user` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  4. `user_pwd` varchar(255) DEFAULT NULL,
  5. `user_type` int(11) DEFAULT NULL,
  6. `parent_id` bigint(20) DEFAULT NULL,
  7. `user_status` int(11) DEFAULT NULL,
  8. `distributor_id` bigint(20) DEFAULT NULL,
  9. `creator_identity_type` int(2) DEFAULT NULL,
  10. `creator_id` bigint(20) DEFAULT NULL,
  11. `create_date` varchar(50) DEFAULT NULL,
  12. PRIMARY KEY (`id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=22 DEFAULT CHARSET=utf8;

设备表

  1. CREATE TABLE `pos_device` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `imei` varchar(120) NOT NULL,
  4. `mac` varchar(120) NOT NULL,
  5. `unique_code` varchar(120) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
  6. `type` varchar(100) DEFAULT NULL,
  7. `system_version` varchar(100) DEFAULT NULL,
  8. `distributor_id` bigint(20) DEFAULT NULL,
  9. `creator_identity_type` int(2) DEFAULT NULL,
  10. `creator_id` bigint(20) DEFAULT NULL,
  11. `create_date` varchar(50) DEFAULT NULL,
  12. PRIMARY KEY (`id`)
  13. ) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8;

用户和设备关联表

  1. CREATE TABLE `pos_user_device_relation` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `device_id` bigint(20) DEFAULT NULL,
  4. `user_id` bigint(20) DEFAULT NULL,
  5. PRIMARY KEY (`id`)
  6. ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;

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

联合查询的需求

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

sql语句

  1. SELECT
  2. pdr.id,
  3. pdr.device_id,
  4. pd.imei,
  5. pd.mac,
  6. pd.unique_code,
  7. pd.type,
  8. pd.system_version,
  9. pdr.user_id,
  10. pu.user_name,
  11. pu.user_type
  12. FROM
  13. pos_user_device_relation pdr, pos_user pu, pos_device pd
  14. 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

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

  1. +----+-----------+---------------------+-------------------+--------------------------+----------+----------------+---------+---------------+-----------+
  2. | id | device_id | imei | mac | unique_code | type | system_version | user_id | user_name | user_type |
  3. +----+-----------+---------------------+-------------------+--------------------------+----------+----------------+---------+---------------+-----------+

如何在JPA中映射为DTO对象

DTO对象字段定义如下:

  1. private Long posUserDeviceId;
  2. private Long deviceId;
  3. private String deviceImei;
  4. private String deviceMac;
  5. private String deviceUniqueCode;
  6. private String deviceType;
  7. private String deviceSystemVersion;
  8. private Long userId;
  9. private String username;
  10. private PosUserEntityConstants.UserType userType;

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

方法一:使用HQL的方法

Repository的查询代码如下:

  1. @Query(
  2. value = "SELECT\n" +
  3. "new com.hengbao.ethiopiatelecomrecharge.dao.dto.PosUserDeviceRelationDto(\n" +
  4. "pdr.id,\n" +
  5. "pdr.deviceId,\n" +
  6. "pd.imei,\n" +
  7. "pd.mac,\n" +
  8. "pd.uniqueCode,\n" +
  9. "pd.type,\n" +
  10. "pd.systemVersion,\n" +
  11. "pdr.userId,\n" +
  12. "pu.userName,\n" +
  13. "pu.userType\n" +
  14. ") \n" +
  15. "FROM \n" +
  16. "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd \n" +
  17. "WHERE pdr.deviceId = pd.id AND pdr.userId = pu.id AND pdr.userId in (select id from PosUserEntity where distributorId=?1)",
  18. countQuery = "SELECT count(*) FROM \n" +
  19. "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd \n" +
  20. "WHERE pdr.deviceId = pd.id AND pdr.userId = pu.id AND pdr.userId in (select id from PosUserEntity where distributorId=?1)"
  21. )
  22. Page<PosUserDeviceRelationDto> findUserAndDeviceInfoByDistributorId(Long distributorId, Pageable pageable);

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

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

  1. @Getter
  2. @Setter
  3. @NoArgsConstructor
  4. @AllArgsConstructor
  5. @ToString
  6. public class PosUserDeviceRelationDto implements Serializable {
  7. /**
  8. * 版本号
  9. */
  10. private static final long serialVersionUID = 1L;
  11. private Long posUserDeviceId;
  12. private Long deviceId;
  13. private String deviceImei;
  14. private String deviceMac;
  15. private String deviceUniqueCode;
  16. private String deviceType;
  17. private String deviceSystemVersion;
  18. private Long userId;
  19. private String username;
  20. private PosUserEntityConstants.UserType userType;
  21. }

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

Repository的查询代码如下:

  1. @Query(
  2. value = "SELECT\n" +
  3. "pdr.id,\n" +
  4. "pdr.device_id,\n" +
  5. "pd.imei,\n" +
  6. "pd.mac,\n" +
  7. "pd.unique_code,\n" +
  8. "pd.type,\n" +
  9. "pd.system_version,\n" +
  10. "pdr.user_id,\n" +
  11. "pu.user_name,\n" +
  12. "pu.user_type\n" +
  13. "FROM\n" +
  14. "pos_user_device_relation pdr, pos_user pu, pos_device pd\n" +
  15. "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)",
  16. countQuery = "SELECT count(*) FROM\n" +
  17. "pos_user_device_relation pdr, pos_user pu, pos_device pd\n" +
  18. "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)",
  19. nativeQuery = true
  20. )
  21. 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的构造器如下:

  1. public PosUserDeviceRelationDto(BigInteger posUserDeviceId,
  2. BigInteger deviceId,
  3. String deviceImei,
  4. String deviceMac,
  5. String deviceUniqueCode,
  6. String deviceType,
  7. String deviceSystemVersion,
  8. BigInteger userId,
  9. String username,
  10. Integer userType) {
  11. this.posUserDeviceId = posUserDeviceId == null ? null : posUserDeviceId.longValue();
  12. this.deviceId = deviceId == null ? null : deviceId.longValue();
  13. this.deviceImei = deviceImei;
  14. this.deviceMac = deviceMac;
  15. this.deviceUniqueCode = deviceUniqueCode;
  16. this.deviceType = deviceType;
  17. this.deviceSystemVersion = deviceSystemVersion;
  18. this.userId = userId == null ? null : userId.longValue();
  19. this.username = username;
  20. // UserTypeConverter是继承自javax.persistence.AttributeConverter的类型转换器
  21. this.userType = new PosUserEntityConstants.UserTypeConverter().convertToEntityAttribute(userType);
  22. }

然后直接调用构造即可:

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

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

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

例子涉及的部分源代码

Repository

  1. @Query(
  2. value = "SELECT\n" +
  3. "new com.hengbao.ethiopiatelecomrecharge.dao.dto.PosUserDeviceRelationDto(\n" +
  4. "pdr.id,\n" +
  5. "pdr.deviceId,\n" +
  6. "pd.imei,\n" +
  7. "pd.mac,\n" +
  8. "pd.uniqueCode,\n" +
  9. "pd.type,\n" +
  10. "pd.systemVersion,\n" +
  11. "pdr.userId,\n" +
  12. "pu.userName,\n" +
  13. "pu.userType\n" +
  14. ") \n" +
  15. "FROM \n" +
  16. "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd \n" +
  17. "WHERE pdr.deviceId = pd.id AND pdr.userId = pu.id AND pdr.userId in (select id from PosUserEntity where distributorId=?1)",
  18. countQuery = "SELECT count(*) FROM \n" +
  19. "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd \n" +
  20. "WHERE pdr.deviceId = pd.id AND pdr.userId = pu.id AND pdr.userId in (select id from PosUserEntity where distributorId=?1)"
  21. )
  22. Page<PosUserDeviceRelationDto> findUserAndDeviceInfoByDistributorId(Long distributorId, Pageable pageable);
  23. @Query(
  24. value = "SELECT\n" +
  25. "pdr.id,\n" +
  26. "pdr.device_id,\n" +
  27. "pd.imei,\n" +
  28. "pd.mac,\n" +
  29. "pd.unique_code,\n" +
  30. "pd.type,\n" +
  31. "pd.system_version,\n" +
  32. "pdr.user_id,\n" +
  33. "pu.user_name,\n" +
  34. "pu.user_type\n" +
  35. "FROM\n" +
  36. "pos_user_device_relation pdr, pos_user pu, pos_device pd\n" +
  37. "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)",
  38. countQuery = "SELECT count(*) FROM\n" +
  39. "pos_user_device_relation pdr, pos_user pu, pos_device pd\n" +
  40. "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)",
  41. nativeQuery = true
  42. )
  43. Page<Object[]> findUserAndDeviceInfoByDistributorId2(Long distributorId, Pageable pageable);

DTO类

  1. @Getter
  2. @Setter
  3. @NoArgsConstructor
  4. @AllArgsConstructor
  5. @ToString
  6. public class PosUserDeviceRelationDto implements Serializable {
  7. /**
  8. * 版本号
  9. */
  10. private static final long serialVersionUID = 1L;
  11. private Long posUserDeviceId;
  12. private Long deviceId;
  13. private String deviceImei;
  14. private String deviceMac;
  15. private String deviceUniqueCode;
  16. private String deviceType;
  17. private String deviceSystemVersion;
  18. private Long userId;
  19. private String username;
  20. private PosUserEntityConstants.UserType userType;
  21. public PosUserDeviceRelationDto(BigInteger posUserDeviceId,
  22. BigInteger deviceId,
  23. String deviceImei,
  24. String deviceMac,
  25. String deviceUniqueCode,
  26. String deviceType,
  27. String deviceSystemVersion,
  28. BigInteger userId,
  29. String username,
  30. Integer userType) {
  31. this.posUserDeviceId = posUserDeviceId == null ? null : posUserDeviceId.longValue();
  32. this.deviceId = deviceId == null ? null : deviceId.longValue();
  33. this.deviceImei = deviceImei;
  34. this.deviceMac = deviceMac;
  35. this.deviceUniqueCode = deviceUniqueCode;
  36. this.deviceType = deviceType;
  37. this.deviceSystemVersion = deviceSystemVersion;
  38. this.userId = userId == null ? null : userId.longValue();
  39. this.username = username;
  40. // UserTypeConverter是继承自javax.persistence.AttributeConverter的类型转换器
  41. this.userType = new PosUserEntityConstants.UserTypeConverter().convertToEntityAttribute(userType);
  42. }
  43. }

test测试类:

  1. @Test
  2. public void testFindUserAndDeviceInfoByDistributorId() throws Exception {
  3. System.out.println("-----------------hql query-----------------");
  4. Page<PosUserDeviceRelationDto> userAndDeviceInfoByDistributorId = posUserDeviceRelationRepository.findUserAndDeviceInfoByDistributorId(1L, PageRequest.of(0, 10));
  5. System.out.println("count=" + userAndDeviceInfoByDistributorId.getTotalElements());
  6. if(userAndDeviceInfoByDistributorId.getContent() != null) {
  7. for (PosUserDeviceRelationDto dto : userAndDeviceInfoByDistributorId.getContent()) {
  8. System.out.println(dto);
  9. }
  10. }
  11. System.out.println("-----------------native sql query-----------------");
  12. Page<Object[]> userAndDeviceInfoByDistributorId2 = posUserDeviceRelationRepository.findUserAndDeviceInfoByDistributorId2(1L, PageRequest.of(0, 10));
  13. System.out.println("count=" + userAndDeviceInfoByDistributorId2.getTotalElements());
  14. if(userAndDeviceInfoByDistributorId2.getContent() != null) {
  15. for (Object[] objects : userAndDeviceInfoByDistributorId2.getContent()) {
  16. for (Object obj : objects) {
  17. System.out.print(obj + "(" + (obj == null ? null : obj.getClass().getSimpleName()) + ") ");
  18. }
  19. System.out.println();
  20. }
  21. // 转换为dto 方法一
  22. System.out.println("-----转换dto的第一种方法-----");
  23. for (Object[] objects : userAndDeviceInfoByDistributorId2.getContent()) {
  24. // 转换成dto的方法一:将objects中的所有参数强转为对应类型,传递到dto的构造器中;dto对象定义好对应的构造器
  25. PosUserDeviceRelationDto dto1 = new PosUserDeviceRelationDto(
  26. (BigInteger) objects[0],
  27. (BigInteger) objects[1],
  28. (String ) objects[2],
  29. (String ) objects[3],
  30. (String ) objects[4],
  31. (String ) objects[5],
  32. (String ) objects[6],
  33. (BigInteger) objects[7],
  34. (String ) objects[8],
  35. (Integer ) objects[9]);
  36. System.out.println(dto1);
  37. }
  38. // 转换为dto 方法二
  39. System.out.println("-----转换dto的第二种方法-----");
  40. for (Object[] objects : userAndDeviceInfoByDistributorId2.getContent()) {
  41. // 转换成dto的方法二:反射的方法直接调用构造
  42. PosUserDeviceRelationDto dto2 = caseDto(objects, new Class[]{BigInteger.class,
  43. BigInteger.class,
  44. String.class,
  45. String.class,
  46. String.class,
  47. String.class,
  48. String.class,
  49. BigInteger.class,
  50. String.class,
  51. Integer.class},
  52. PosUserDeviceRelationDto.class);
  53. System.out.println(dto2);
  54. }
  55. }
  56. }
  57. /**
  58. * https://blog.csdn.net/qq_36144258/article/details/80296512
  59. * 网页中直接使用objectArray中获取每一个class,但是这样有一个问题,就是如果获取的objectArray中有一个空值的话,不能获取到class,
  60. * 导致不能获取到对象的构造器
  61. * @param objectArray
  62. * @param objectClassArray
  63. * @param dtoClass
  64. * @param <T>
  65. * @return
  66. */
  67. private <T> T caseDto(Object[] objectArray, Class[] objectClassArray, Class<T> dtoClass) throws Exception {
  68. Constructor<T> constructor = dtoClass.getConstructor(objectClassArray);
  69. return constructor.newInstance(objectArray);
  70. }

JPA将查询结果转换为DTO对象的更多相关文章

  1. DataTable与DTO对象的简易转换类

    在web开发过程中,有时候为了数据传输的方便,比如:后台需要更新前端的ViewModel,此时我们定义一个与前端ViewModel结构一样的DTO对象,从数据层获取数据后,将数据封装成DTO然后序列化 ...

  2. springboot~@Query到DTO对象

    我们有时在进行开发过程中,使用jpa的@Query注解去选择多张表然后返回一个DTO对象,这个时候我们需要特殊处理一下,因为默认情况下,你的jpa代码是不认DTO对象的. 参考文章:https://s ...

  3. Hibernate三种状态;query查询;ResultTransformer转换为pojo对象;能够将query语句写在xml中;Criteria查询;ProjectionList总和/f分组等函数

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/u010026901/article/details/24256091 Session操作过程中的po ...

  4. spring data JPA entityManager查询 并将查询到的值转为实体对象

    spring data JPA entityManager查询 并将查询到的值转为实体对象 . https://blog.csdn.net/qq_34791233/article/details/81 ...

  5. modelMapper使用,将数据库查询对象直接转成DTO对象

    1.pom引入 <dependency> <groupId>org.modelmapper</groupId> <artifactId>modelmap ...

  6. JPA 复杂查询 - Querydsl

     添加依赖 <!--query dsl --> <dependency> <groupId>com.querydsl</groupId> <art ...

  7. DTO对象

    在EF中,EF生成的对象都是代理对象,这些对象看上去是实体类对象,但是其实都是EF封装好的代理类对象.所以调用EF查询得到的代理类对象有继承于实体对象,所以可以用实体类对象来接收返回的代理类对象.EF ...

  8. hibernate将本地SQL查询结果封装成对象

    hibernate将本地SQL查询结果封装成对象 不知道大家有没有碰过这种情况,迫于很多情况只能用native SQL来查询(如:复杂统计等),然而使用native查询后,结果会被放到object里, ...

  9. Spring Data JPA 简单查询--接口方法

    一.接口方法整理速查 下表针对于简单查询,即JpaRepository接口(继承了CrudRepository接口.PagingAndSortingRepository接口)中的可访问方法进行整理.( ...

随机推荐

  1. Postman使用-2

    转载:https://www.cnblogs.com/yunman/p/7884537.html Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件. 接口请求流程 一.g ...

  2. Protocol Buffers官方文档(开发指南)

    本文是对官方文档的翻译,然后截取了一篇非常优秀的文章片段来帮助理解,本人英文水平有限,基本都是直译,如果有不理解的地方请参考英文官方文档,参考的文章链接在文章末尾 protocol buffers简介 ...

  3. Docker删除私有仓库镜像

    V2 安装删除脚本 # curl https://raw.githubusercontent.com/burnettk/delete-docker-registry-image/master/dele ...

  4. CodeForces 114B 【STL应用】

    思路: 原来string类能sort 和 swap....太强了.... 注意:字典序最小输出,因为某个地方写挫了,sort了n发,代码挫. #include <bits/stdc++.h> ...

  5. EIGRP-2-EIGRP的度量

    EIGRP使用多种类型的度量值来描述一条路由的不同技术属性,称为组合度量或度量组合.这些组合度量参数包括带宽.延迟.可靠性.负载.MTU和跳数.这6个参数中的前4个通过一个著名的公式组合在一起,计算出 ...

  6. POJ 2318 TOYS 利用叉积判断点在线段的那一侧

    题意:给定n(<=5000)条线段,把一个矩阵分成了n+1分了,有m个玩具,放在为位置是(x,y).现在要问第几个位置上有多少个玩具. 思路:叉积,线段p1p2,记玩具为p0,那么如果(p1p2 ...

  7. leetcoe--47. Permutations II

    1.问题描述 Given a collection of numbers that might contain duplicates, return all possible unique permu ...

  8. 双层列表 datagrid里属性

    frozenColumns: [ [{ title: "姓名"}] ], columns: [ [{"title":"延时原因"}], [{ ...

  9. 3 - EventLoop和线程模型-事件循环

    a). EventLoopGroup为每个新创建的channel分配一个EventLoop,多个channel对应一个EventLoop. b). 一个EventLoop由一个不变的thread驱动, ...

  10. 这个五月,我拿到了腾讯暑期offer

    前情回顾 本来学校就是双非,如果再没有些亮点,是很难进大厂的. 我比较幸运,曾在网易实习过一段时间,很感谢这段经历,让我有幸通过腾讯HR们的简历筛选. 对于腾讯春招,其实我是没有参加网申(2.28-3 ...