工具类相关代码
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface JoinProperty {
/**
* 用VO哪个字段查
*/
String source();
/**
* 目标表对应的实体
*/
Class joinEntity();
/**
* 目标查询字段,默认会取id字段
* @return
*/
String on() default "";
/**
* 取目标表的哪个字段,默认取VO上当前注解的字段名
*/
String select() default "";
/**
* 默认值
* @return
*/
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface Mapping {
/**
* 实体源字段,用于转VO
* @return
*/
String source() default "";
/**
* 实体目标字段,用于DTO转到实体
* @return
*/
String target() default "";
}
@UtilityClass
public class BeanCopyUtils {
public String[] getNullPropertyNames(Object source) {
final BeanWrapper src = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors();
Set<String> emptyNames = new HashSet<String>();
for (java.beans.PropertyDescriptor pd : pds) {
Object srcValue = src.getPropertyValue(pd.getName());
if (srcValue == null) {
emptyNames.add(pd.getName());
}
}
String[] result = new String[emptyNames.size()];
return emptyNames.toArray(result);
}
/**
* 拷贝对象属性并忽略null的属性
* @param src
* @param target
*/
public void copyProperties(Object src, Object target) {
if(src == null) {
return;
}
BeanUtils.copyProperties(src, target, getNullPropertyNames(src));
}
/**
* 将list中的对象替换成vo对象
* @param list
* @param clazz
* @return
*/
@SneakyThrows
public <T>List<T> replaceWithVO(List list, Class clazz) {
if(list!=null&&list.size()>0){
List newList=new ArrayList(list.size());
for (Object o : list) {
Object newObject=clazz.newInstance();
copyProperties(o,newObject);
newList.add(newObject);
}
process(list,newList, clazz);
for(int i=0;i<list.size();i++){
list.set(i,newList.get(i));
}
}
return list;
}
/**
* List的vo转换
* @param list
* @param clazz
* @return
*/
@SneakyThrows
public <T>List<T> turnToVOList(List list,Class clazz) {
List<T> newResult = new ArrayList();
if(list!=null&&list.size()>0){
newResult=new ArrayList(list.size());
for (Object o : list) {
Object newObject=clazz.newInstance();
copyProperties(o,newObject);
newResult.add((T)newObject);
}
process(list,newResult, clazz);
}
return newResult;
}
/**
* vo转换
* @param o
* @param clazz
* @param <T>
* @return
*/
@SneakyThrows
public <T>T turnToVO(Object o,Class<T> clazz) {
if(o!=null){
T newObject=clazz.newInstance();
copyProperties(o,newObject);
process(ListUtil.toList(o),ListUtil.toList(newObject),clazz);
return newObject;
}
return null;
}
/**
* 装填JoinProperty数据
* @param list
* @param clazz
*/
@SneakyThrows
public void fillJoinProperty(List list,Class clazz){
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
JoinProperty joinProperty = field.getAnnotation(JoinProperty.class);
if (joinProperty != null) {
Set idSet=new HashSet();
for (Object o : list) {
Object id=clazz.getMethod("get"+ StrUtil.upperFirst(joinProperty.source())).invoke(o);
if(id!=null){
idSet.add(id);
}
}
if(idSet.size()>0){
String mapperName=joinProperty.joinEntity()+"Mapper";
Object mapper= SpringContextHolder.getBean(StrUtil.lowerFirst(mapperName));
String targetColumn="".equals(joinProperty.select())?field.getName():joinProperty.select();
String targetQuery="".equals(joinProperty.on())?"id":joinProperty.on();
Class queryClass=Class.forName("com.baomidou.mybatisplus.core.conditions.query.QueryWrapper");
Object query=queryClass.newInstance();
queryClass.getMethod("select",String[].class).invoke(query,(Object)new String[]{StrUtil.toUnderlineCase(targetQuery),StrUtil.toUnderlineCase(targetColumn)});
queryClass.getMethod("in",Object.class, Collection.class).invoke(query,StrUtil.toUnderlineCase(targetQuery),idSet);
List entityList=(List)mapper.getClass().getMethod("selectList",Class.forName("com.baomidou.mybatisplus.core.conditions.Wrapper")).invoke(mapper,query);
Map entityMap=new HashMap(entityList.size());
for (Object entity : entityList) {
entityMap.put(entity.getClass().getMethod("get"+StrUtil.upperFirst(targetQuery)).invoke(entity),entity.getClass().getMethod("get"+StrUtil.upperFirst(targetColumn)).invoke(entity));
}
for (Object o : list) {
Object id=clazz.getMethod("get"+StrUtil.upperFirst(joinProperty.source())).invoke(o);
Object value=entityMap.get(id);
field.setAccessible(true);
if(value==null&&!"\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n".equals(joinProperty.defaultValue())){
field.set(o, Convert.convert(field.getType(),joinProperty.defaultValue()));
}else{
field.set(o,value);
}
}
}
}
}
}
/**
* 附加属性处理
* @param source
* @param list
* @param clazz
*/
@SneakyThrows
private void process(List source,List list,Class clazz){
Class sourceClass = source.get(0).getClass();
Field[] sourceFields = sourceClass.getDeclaredFields();
for (Field sourceField : sourceFields) {
Mapping mapping=sourceField.getAnnotation(Mapping.class);
if(mapping!=null&&mapping.target().length()>0){
for (int i=0;i<list.size();i++) {
Object o=list.get(i);
Object sourceObject=source.get(i);
Object value = sourceClass.getMethod("get" + StrUtil.upperFirst(sourceField.getName())).invoke(sourceObject);
clazz.getMethod("set"+StrUtil.upperFirst(mapping.target()),value.getClass()).invoke(o,value);
}
}
}
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Mapping mapping=field.getAnnotation(Mapping.class);
if(mapping!=null&&mapping.source().length()>0){
for (int i=0;i<list.size();i++) {
Object o=list.get(i);
Object sourceObject=source.get(i);
Object value=sourceClass.getMethod("get"+StrUtil.upperFirst(mapping.source())).invoke(sourceObject);
if(value!=null){
field.setAccessible(true);
field.set(o,value);
}
}
}
}
fillJoinProperty(list,clazz);
}
}
Entity转VO工具BeanCopyUtils使用技巧
BeanCopyUtils主要用于Entity转VO,同时可对VO中的附加字段从数据库中进行关联查询。主要的使用方式如下:
案例一,单个Entity转成VO
UserInfo userInfo=userInfoService.getById(12325465874314113L);
UserInfoVO vo=BeanCopyUtils.turnToVO(userInfo,UserInfoVO.class);
案例二,Entity列表转成VO列表
List<UserInfo> userInfoList=userInfoService.list();
List<UserInfoVO> voList=BeanCopyUtils.turnToVOList(userInfoList,UserInfoVO.class);
案例三,将分页对象Page中的Entity列表转成VO
Page<UserInfo> page=userInfoService.page();
BeanCopyUtils.replaceWithVO(page.getRecords(),UserInfoVO.class);
注意:案例三中其实都不用将转换后的对象赋值给一个变量,因为执行replaceWithVO方法后原List中的对象已经被替换了,直接将原List或Page返回给前端就行了,除非需要对VO的列表结果还需要进行下一步操作。
案例四,VO中的某些字段与源实体中的字段名不一致
有些时候VO中的某些字段与源实体中的字段名不一样,需要手动在逻辑代码中进行转换,这个时候只需要通过Mapping注解进行标记即可,注意:
public class UserInfoVO{
/**
* 用户id
*/
@Mapping(source="id")
private Long userId;
}
案例五,VO中的某些附加字段需要从数据库中级联查询
假设UserInfoVO长这样,里面有个部门id deptId
public class UserInfoVO{
//其他的字段忽略
/**
* 部门id
*/
private Long deptId;
}
这时,我们希望展示给前端的结果中能把部门的名字展示出来,一般我们会给UserInfoVO加一个字段
public class UserInfoVO{
//其他的字段忽略
/**
* 部门id
*/
private Long deptId;
/**
* 部门名称
*/
private Long deptName;
}
然后在将查询结果转成VO后再从部门表中根据部门id查出部门信息塞到VO中
//单条记录
UserInfo userInfo=userInfoService.getById(12325465874314113L);
UserInfoVO vo=BeanCopyUtils.turnToVO(userInfo,UserInfoVO.class);
DeptInfo deptInfo=deptInfoService.getById(vo.getDeptId);
vo.setDeptName(deptInfo.getDeptName());
//多条记录
List<UserInfo> userInfoList=userInfoService.list();
List<UserInfoVO> voList=BeanCopyUtils.replaceWithVO(userInfoList,UserInfoVO.class);
for(UserInfoVO vo:voList){
DeptInfo deptInfo=deptInfoService.getById(vo.getDeptId);
vo.setDeptName(deptInfo.getDeptName());
}
//上面多条记录性能不好,查询次数会随着userInfoList的数量的增加而增加
//多条记录优化
List<UserInfo> userInfoList=userInfoService.list();
List<UserInfoVO> voList=BeanCopyUtils.replaceWithVO(userInfoList,UserInfoVO.class);
Set<Long> ids=voList.stream().map(UserInfoVO::getDeptId).collect(Collectors.toSet());
List<DeptInfo> deptInfoList=deptInfoService.listByIds(ids);
Map<Long,String> map=deptInfoList.stream().collect(Collectors.toMap(DeptInfo::getId,DeptInfo::getDeptName));
for(UserInfoVO vo:voList){
vo.setDeptName(map.get(vo.getDeptId()));
}
上面的案例中我们可以看到步骤比较繁杂,而且还没有进行空指针判断代码就已经看起来比较多,所以针对上面这种情况我们可以通过添加一个@JoinProperty注解即可
public class UserInfoVO{
//其他的字段忽略
/**
* 部门id
*/
private Long deptId;
/**
* 部门名称
*/
@JoinProperty(joinEntity = "DeptInfo", source ="deptId",on = "id",select = "deptName")
private Long deptName;
}
这样上面那些繁杂的代码都不需要了,就执行turnToVO()方法即可。
说明:@JoinProperty注解中的joinEntity指需要关联的表对应的表名,source指用VO中的哪个字段去查,on指查的时候对应关联表的条件字段是什么,select指 取关联实体中的哪个字段。
上面案例中由于查的是id,所以on参数可以不写,因为默认查id。然后VO中的deptName和DeptInfo中的deptName名字一致,所以select也可以不写。
public class UserInfoVO{
//其他的字段忽略
/**
* 部门id
*/
private Long deptId;
/**
* 部门名称
*/
@JoinProperty(joinEntity = DeptInfo.class, source ="deptId")
private Long deptName;
}
如果字段没有映射到数据,想设置一个默认值,则可使用defaultValue参数
public class UserInfoVO{
//其他的字段忽略
/**
* 部门id
*/
private Long deptId;
/**
* 部门名称
*/
@JoinProperty(joinEntity = DeptInfo.class, source ="deptId", defaultValue="默认部门")
private Long deptName;
}