zoukankan      html  css  js  c++  java
  • 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
    " +
                    "new com.hengbao.ethiopiatelecomrecharge.dao.dto.PosUserDeviceRelationDto(
    " +
                    "pdr.id,
    " +
                    "pdr.deviceId,
    " +
                    "pd.imei,
    " +
                    "pd.mac,
    " +
                    "pd.uniqueCode,
    " +
                    "pd.type,
    " +
                    "pd.systemVersion,
    " +
                    "pdr.userId,
    " +
                    "pu.userName,
    " +
                    "pu.userType
    " +
                    ") 
    " +
                    "FROM 
    " +
                    "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd 
    " +
                    "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 
    " +
                    "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd 
    " +
                    "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
    " +
                    "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)",
            countQuery = "SELECT count(*) 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)",
            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://blog.csdn.net/qq_36144258/article/details/80296512),但是csdn上这个存在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);
    }
    
    /**
     * https://blog.csdn.net/qq_36144258/article/details/80296512
     * 网页中直接使用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
    " +
                    "new com.hengbao.ethiopiatelecomrecharge.dao.dto.PosUserDeviceRelationDto(
    " +
                    "pdr.id,
    " +
                    "pdr.deviceId,
    " +
                    "pd.imei,
    " +
                    "pd.mac,
    " +
                    "pd.uniqueCode,
    " +
                    "pd.type,
    " +
                    "pd.systemVersion,
    " +
                    "pdr.userId,
    " +
                    "pu.userName,
    " +
                    "pu.userType
    " +
                    ") 
    " +
                    "FROM 
    " +
                    "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd 
    " +
                    "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 
    " +
                    "PosUserDeviceRelationEntity pdr, PosUserEntity pu, PosDeviceEntity pd 
    " +
                    "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
    " +
                    "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)",
            countQuery = "SELECT count(*) 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)",
            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);
            }
        }
    }
    
    /**
     * https://blog.csdn.net/qq_36144258/article/details/80296512
     * 网页中直接使用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);
    }
    

    作者:itlgl
    出处:http://www.cnblogs.com/itlgl/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利

  • 相关阅读:
    How to install VXDIAG Honda, Toyota and JLR SDD software
    16% off MPPS V16 ECU tuning tool for EDC15 EDC16 EDC17
    Cummins INSITE locked and ask for verification code
    How to use BMW Multi Tool 7.3 to replace lost key for BMW X1
    Bleed Brake Master Cylinder with Intelligent Tester IT2
    Porsche Piwis Tester II “No VCI has been detected”,how to do?
    Creader VIII VS. Creader VII+
    How to solve GM MDI cannot complete the installation
    汽车OBD2诊断程序开发 (原文转载,思路很清晰!)
    汽车节温器单片机开发思路
  • 原文地址:https://www.cnblogs.com/itlgl/p/10563618.html
Copyright © 2011-2022 走看看