在使用Spring Data Jpa框架时,根据业务需求我们通常需要进行复杂的数据库查询,并返回我们自定义的实体类,而在该框架下,目前仅仅支持返回与数据库映射进行持久化的POJO实体。虽然在框架上我们可以使用@Query注解执行我们自定义的sql语句,但是其返回值为List<Object[]> 类型,即多个Object数组的List集合。
下面我们介绍一下关于在Spring Data Jpa框架下使用自定义查询语句返回自定义实体的解决方案。
解决方案一:
例如我们有如下相关联实体:
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 = "机构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 = "岗位机构") 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 = "机构id") private Long branchId; @Column(name = "branch_name",nullable = false) @NotBlank @ApiModelProperty(value = "机构名称") private String branchName; @Column(name = "level",nullable = false) @NotNull @ApiModelProperty(value = "级别") private Integer level; @Column(name = "p_id",nullable = false) @NotNull @ApiModelProperty(value = "父级机构id") private Long pId; @Column(name = "dept_sort",nullable = false) @NotNull @ApiModelProperty(value = "部门排序级别") private Integer deptSort; }}
要转化成的实体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,属性的设置,是表明该方法中的sql以数据库的sql语句格式对待。
②返回值为List<Object[]>,由于我们之前说过Jpa无法自动完成查询结果到自定义实体的映射,所以我们要使用该对象接收。
最后我们看下将该List<Object[]>对象转换为我们自定义实体的工具类:
public class EntityUtils { private static Logger logger = LoggerFactory.getLogger(EntityUtils.class); /** * 将数组数据转换为实体类 * 此处数组元素的顺序必须与实体类构造函数中的属性顺序一致 * * @param list 数组对象集合 * @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; } //获取每个数组集合的元素个数 Object[] co = list.get(0); //获取当前实体类的属性名、属性值、属性类别 List<Map> attributeInfoList = getFiledsInfo(model); //创建属性类别数组 Class[] c2 = new Class[attributeInfoList.size()]; //如果数组集合元素个数与实体类属性个数不一致则发生错误 if (attributeInfoList.size() != co.length) { return returnList; } //确定构造方法 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("实体数据转化为实体类发生异常:异常信息:{}", ex.getMessage()); return returnList; } return returnList; } /** * 根据属性名获取属性值 * * @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; }}
在执行DAO层方法,获得相应的List<Object[]>对象调用工具类中的静态方法castEntity,即可将数据转换为自定义实体。
在使用该解决方案时,需注意以下几点要求:
①自定义查询语句中的查询字段的顺序一定要和自定义实体的构造方法中的属性顺序一致。
②此种方案在解决特别复杂的查询语句时很高效,因为只需自定义查询语句,与数据库进行一次交互即可,效率可观。但对程序的规范性要求比较高。
③此方案在解决当前项目数据库中数据表在业务需求下创建而不符合使用JPA框架创建持久化实体之间的关联关系(即因为业务需求,所建立库表不符合数据库建库规范),而又需要进行多表关联进行复杂查询时,很实用。
特别说明:上面所举的例子只是单纯为了演示此方案,因为上面表关联之简单要获得如上的结果使用JPA框架也可轻松实现。
具体转化demo如下:
/**object对象转化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查询出来的结果:
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
第二步:Repository层用List<Map<String,String>>接收返回的数据
@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);
第三步进行转化:
List<Map<String,String>> evaUserResp = userRepository.findByUserId(x.getUserId()); String irsStr = JSON.toJSONString(evaUserResp); List<EvaUserResp> evaUserResps = JSON.parseArray(irsStr,EvaUserResp.class);