JPA將查詢結果轉換爲DTO對象

前言

JPA支持使用@Query自定義查詢,查詢的結果須要字節用DTO對象接收,若是使用HQL的查詢語句,能夠將直接將DTO對象的構造方法傳入hql中,直接轉爲DTO對象;而若是使用native sql查詢的方式,只能將返回結果用Object[]對象接收,而後DTO設置對象的構造來接收Object[]裏面的參數完成DTO對象的轉換。java

例子

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和設備idmysql

聯合查詢的需求

想列出pos_user_device_relation表中全部pos_user的distributor_id=1的全部用戶和設備,要求返回的信息包括用戶的username、type信息和設備的imei、mac等信息。sql

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的枚舉。ide

方法一:使用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的註解會自動生成一個全參數的構造器,構造器的順序和字段定義順序一致,類代碼以下:this

@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的查詢代碼以下:.net

@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的構造器以下:code

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\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);
        }
    }
}

/**
 * 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);
}
相關文章
相關標籤/搜索