在使用Spring Data Jpa框架時,根據(jù)業(yè)務(wù)需求我們通常需要進(jìn)行復(fù)雜的數(shù)據(jù)庫查詢,并返回我們自定義的實體類,而在該框架下,目前僅僅支持返回與數(shù)據(jù)庫映射進(jìn)行持久化的POJO實體。雖然在框架上我們可以使用@Query注解執(zhí)行我們自定義的sql語句,但是其返回值為List<Object[]> 類型,即多個Object數(shù)組的List集合。
下面我們介紹一下關(guān)于在Spring Data Jpa框架下使用自定義查詢語句返回自定義實體的解決方案。
解決方案一:
例如我們有如下相關(guān)聯(lián)實體:
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 = "機(jī)構(gòu)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 = "崗位機(jī)構(gòu)")
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 = "機(jī)構(gòu)id")
private Long branchId;
@Column(name = "branch_name",nullable = false)
@NotBlank
@ApiModelProperty(value = "機(jī)構(gòu)名稱")
private String branchName;
@Column(name = "level",nullable = false)
@NotNull
@ApiModelProperty(value = "級別")
private Integer level;
@Column(name = "p_id",nullable = false)
@NotNull
@ApiModelProperty(value = "父級機(jī)構(gòu)id")
private Long pId;
@Column(name = "dept_sort",nullable = false)
@NotNull
@ApiModelProperty(value = "部門排序級別")
private Integer deptSort;
}
}
要轉(zhuǎn)化成的實體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,屬性的設(shè)置,是表明該方法中的sql以數(shù)據(jù)庫的sql語句格式對待。
②返回值為List<Object[]>,由于我們之前說過Jpa無法自動完成查詢結(jié)果到自定義實體的映射,所以我們要使用該對象接收。
最后我們看下將該List<Object[]>對象轉(zhuǎn)換為我們自定義實體的工具類:
public class EntityUtils {
private static Logger logger = LoggerFactory.getLogger(EntityUtils.class);
/**
* 將數(shù)組數(shù)據(jù)轉(zhuǎn)換為實體類
* 此處數(shù)組元素的順序必須與實體類構(gòu)造函數(shù)中的屬性順序一致
*
* @param list 數(shù)組對象集合
* @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;
}
//獲取每個數(shù)組集合的元素個數(shù)
Object[] co = list.get(0);
//獲取當(dāng)前實體類的屬性名、屬性值、屬性類別
List<Map> attributeInfoList = getFiledsInfo(model);
//創(chuàng)建屬性類別數(shù)組
Class[] c2 = new Class[attributeInfoList.size()];
//如果數(shù)組集合元素個數(shù)與實體類屬性個數(shù)不一致則發(fā)生錯誤
if (attributeInfoList.size() != co.length) {
return returnList;
}
//確定構(gòu)造方法
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("實體數(shù)據(jù)轉(zhuǎn)化為實體類發(fā)生異常:異常信息:{}", ex.getMessage());
return returnList;
}
return returnList;
}
/**
* 根據(jù)屬性名獲取屬性值
*
* @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;
}
}
在執(zhí)行DAO層方法,獲得相應(yīng)的List<Object[]>對象調(diào)用工具類中的靜態(tài)方法castEntity,即可將數(shù)據(jù)轉(zhuǎn)換為自定義實體。
在使用該解決方案時,需注意以下幾點要求:
①自定義查詢語句中的查詢字段的順序一定要和自定義實體的構(gòu)造方法中的屬性順序一致。
②此種方案在解決特別復(fù)雜的查詢語句時很高效,因為只需自定義查詢語句,與數(shù)據(jù)庫進(jìn)行一次交互即可,效率可觀。但對程序的規(guī)范性要求比較高。
③此方案在解決當(dāng)前項目數(shù)據(jù)庫中數(shù)據(jù)表在業(yè)務(wù)需求下創(chuàng)建而不符合使用JPA框架創(chuàng)建持久化實體之間的關(guān)聯(lián)關(guān)系(即因為業(yè)務(wù)需求,所建立庫表不符合數(shù)據(jù)庫建庫規(guī)范),而又需要進(jìn)行多表關(guān)聯(lián)進(jìn)行復(fù)雜查詢時,很實用。
特別說明:上面所舉的例子只是單純?yōu)榱搜菔敬朔桨?,因為上面表關(guān)聯(lián)之簡單要獲得如上的結(jié)果使用JPA框架也可輕松實現(xiàn)。
具體轉(zhuǎn)化demo如下:
/**object對象轉(zhuǎn)化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查詢出來的結(jié)果:
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>>接收返回的數(shù)據(jù)
@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);
第三步進(jìn)行轉(zhuǎn)化:
List<Map<String,String>> evaUserResp = userRepository.findByUserId(x.getUserId());
String irsStr = JSON.toJSONString(evaUserResp);
List<EvaUserResp> evaUserResps = JSON.parseArray(irsStr,EvaUserResp.class);
感謝你看到這里,我是程序員麥冬,一個java開發(fā)從業(yè)者,深耕行業(yè)六年了,每天都會分享java相關(guān)技術(shù)文章或行業(yè)資訊
歡迎大家關(guān)注和轉(zhuǎn)發(fā)文章,后期還有福利贈送!