使用JPA自定義VO類型轉換(EntityUtils工具類)
JPA自定義VO類型轉換(EntityUtils工具類)
在JPA查詢中,如果需要返回自定義的類,可以使用EntityUtils工具類,該類源碼:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author 954L * @create 2019/10/30 17:27 */ public class EntityUtils { private static final Logger log = 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) { log.error("實體數據轉化為實體類發生異常:異常信息:{}", ex.getMessage()); return returnList; } return returnList; } 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; } } 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; } }
使用原生sql查詢:
/** * 根據公司名稱查詢崗位 * @param name 公司名稱 * @return List<EmploymentPosition> */ @Query(value = "select id, position, salary, people, experience, address, update_time from employment_position where company_name = ?1 and is_delete is false ORDER BY sort asc", nativeQuery = true) List<Object[]> findByCompanyName(String name);
使用類型轉換:
@Override public List<EmploymentPositionVO> findByCompanyName(String name) { List<Object[]> objects = employmentPositionRepository.findByCompanyName(name); return EntityUtils.castEntity(objects, EmploymentPositionVO.class, new EmploymentPositionVO()); }
VO類如下:
import lombok.Data; import java.math.BigInteger; import java.sql.Timestamp; /** * @website https://el-admin.vip * @description / * @author budezhenjia * @date 2021-02-08 **/ @Data public class EmploymentPositionVO { /** ID */ private BigInteger id; /** 崗位名稱 */ private String position; /** 月薪 */ private String salary; /** 人數 */ private Integer people; /** 工作經驗 */ private String experience; /** 工作地點 */ private String address; /** 更新時間 */ private Timestamp updateTime; public EmploymentPositionVO(BigInteger id, String position, String salary, Integer people, String experience, String address, Timestamp updateTime) { this.id = id; this.position = position; this.salary = salary; this.people = people; this.experience = experience; this.address = address; this.updateTime = updateTime; } public EmploymentPositionVO() { } }
註意!
查詢sql語句所查詢出來的字段應與VO類中屬性順序一致,類型也需要一致!!
例如ID這個字段MySQL中類型為bigint,VO類中的類型為bigInteger
dto,vo,po,bo等實體轉換工具類
3層開發以及不是多麼新穎的開發思想瞭,但是呢,苦於開發的程序猿們,經常會被各個實例之間的轉換弄得暈頭轉向,尤其是轉換的次數多瞭,一下就蒙瞭,不知道轉到哪裡去瞭,博主也有這種困難,於是在網上到處找,找到瞭一些方法,並結合自身的開發使用,填補一些坑,希望對大傢有所幫助!
下面宣佈這次的主角:dozer
他是誰,一看英文名就不懂吧,其實他是一個大傢都知道的一個角色,spring裡面他可是傢喻戶曉的一個主角,沒錯就是beanUtils(其實,就是他的替身!)主要作用就是用來復制 JavaBean 屬性的類庫,什麼叫復制,沒錯,就一模一樣的再來一份,但是這樣有一點點小小的區別,那就是,在使用的時候,需要指定一個“容器”(瞎說的,就是一個映射接受對象,也可以叫做目標)來存放,不然,復制到哪去,是不是。
這個時候呀,就有一個經紀人的出現,需要通過“經紀人”去代理復制,才能叫這個替身出來呀(專業替身30年,必須要有經紀人)
<dependency> <groupId>net.sf.dozer</groupId> <artifactId>dozer</artifactId> <version>5.5.1</version> </dependency>
好瞭,經紀人的名片已經發出,這個時候,找到劇組,咱們看看這個替身能不能勝任,能應用在哪些劇組,哪些場景!
第一種場景,完全一樣:(咦,度一樣瞭,那肯定好弄,又看不出來區別)
在各個實體是一樣的情況下,是很容易的,直接上不就行啦,也不用做啥處理是不是:
直接是用原始替身(API方法)
Mapper mapper = new DozerBeanMapper(); DestinationObject destObject = mapper.map(sourceObject, DestinationObject.class);
嘿嘿,換一下衣服,直接OK瞭(也就是使用mapper轉換復制)
第二種場景,完全不一樣:
求乞,完全不一樣,這咋的弄呀,肯定得換替身,是不是!最起碼找個相似的嘛(不用擔心,咱們有化妝師呀,化妝走起!)
@Data public class UserVo{ @Mapping("userName") private String name; @Mapping("password") private String pwd; }
看一下,是不是這個東西,好眼熟,沒錯,是咱們的vo
@Data @TableName("user") public class UserEntity implements Serializable { @ApiModelProperty(value = "id") @TableId(value = "id", type = IdType.INPUT) private String id; @ApiModelProperty(value = "用戶名") @Mapping("name") private String userName; @ApiModelProperty(value = "密碼") @Mapping("pwd") private String password; @ApiModelProperty(value = "登錄名") private String loginName; @ApiModelProperty(value = "創建時間") private Date createTime; @ApiModelProperty(value = "修改時間") private Date updateTime; @ApiModelProperty(value = "版本號") private Integer version; @ApiModelProperty(value = "作廢標記") private Integer deleted; }
這個呢,是咱們的實體對象(也就是數據庫對象瞭)
看一下,是不是發小完全不一樣呀!
這裡呢,@Mappin充當瞭化妝品的角色,將每一個不同的細節進行遮蓋,使其成為和原來的實例一模一樣(也就是做瞭映射瞭)
@Mapping("userName") private String name;
上面就是一個化妝處理,將name映射為userName
每一個地方處理完成後,直接上劇組,看看能不能不被發現
/** * 轉換實體為另一個指定的實體 * 任意一個參數為NULL時 會拋出NPE * * @param source 源實體 不能為NULL * @param clazz 目標實體 不能為NULL * @param <T> 泛型 * @return 轉換後的結果 */ @NonNull public static <T> T convert(@NonNull Object source, @NonNull Class<T> clazz) { return dozerMapper.map(source, clazz); }
實例:
UserEntity userEntity = userMapper.selectOne(wrapper); UserVo convert = null; if(userEntity != null){ convert = Dozer.convert(userEntity, UserVo.class); }
沒錯,這樣就完成轉換替身的操作瞭,結果是可以映射上的,各位可以試試哦!
第三種場景,一堆的不相同的替身(好難呀,一堆的不一樣,不能找幾個差不多的嗎?)
針對於這種一堆的不一樣的替身,dozer也有辦法,通過JAVA8的stream流來進行化妝(化妝師牛,隻能這麼說)
/** * 轉換List實體為另一個指定的實體 * source如果為NULL 會使用空集合 * 在目標實體為NULL時 會拋出NPE * * @param source 源集合 可以為NULL * @param clazz 目標實體 不能為NULL * @param <T> 泛型 * @return 轉換後的結果 */ @Nullable public static <T> List<T> convert(@Nullable List<?> source, @NonNull Class<T> clazz) { return Optional.ofNullable(source) .orElse(Collections.emptyList()) .stream() .map(bean -> dozerMapper.map(bean, clazz)) .collect(Collectors.toList()); }
看見沒,這樣一弄,就OK瞭,咦,是不是發現化妝師dozerMapper從哪來的(這麼牛的化妝師,召幾個開化妝店去),博主告訴你,這個化妝師呀,並不是越多也好的(大傢都知道,一樣的對象,建立太多浪費空間),隻需要建立一個全局唯一的就行瞭,博主帶你們看一下化妝間就明白瞭(工具類來瞭)
package cn.yichehuoban.ycbb.platform.util.beanutils; import org.dozer.Mapper; import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; @Component public class Dozer { /** * dozer轉換的核心mapper對象 */ public static final Mapper dozerMapper = new org.dozer.DozerBeanMapper(); /** * 轉換實體為另一個指定的實體 * 任意一個參數為NULL時 會拋出NPE * * @param source 源實體 不能為NULL * @param clazz 目標實體 不能為NULL * @param <T> 泛型 * @return 轉換後的結果 */ @NonNull public static <T> T convert(@NonNull Object source, @NonNull Class<T> clazz) { return dozerMapper.map(source, clazz); } /** * 轉換List實體為另一個指定的實體 * source如果為NULL 會使用空集合 * 在目標實體為NULL時 會拋出NPE * * @param source 源集合 可以為NULL * @param clazz 目標實體 不能為NULL * @param <T> 泛型 * @return 轉換後的結果 */ @Nullable public static <T> List<T> convert(@Nullable List<?> source, @NonNull Class<T> clazz) { return Optional.ofNullable(source) .orElse(Collections.emptyList()) .stream() .map(bean -> dozerMapper.map(bean, clazz)) .collect(Collectors.toList()); } }
在給大傢看一下我們的替身
@Data public class UserVo{ @Mapping("userName") private String name; @Mapping("password") private String pwd; private String loginName; private Integer version; private Integer deleted; private String id; }
一樣的地方直接就映射上瞭,不一樣的地方使用 @Mapping註解,填寫上源指定對象的字段名就行
這隻是幾種方法,還有其他雙向映射,數據拷貝等,可以看看他的官方文檔dozer
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。