背景
实体之间进行covert操作时,如果使用函数将会出现大量的set、get方法,代码很丑陋。
查阅到两种优化方案。
lombok提供的mapping注解
参考
语法官方说明:https://mapstruct.org/documentation/stable/reference/html/#invoking-custom-mapping-method
完整引入参考:Lombok
与MapStruct实现实体映射
其他用法
@Mapper(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface StudentConverter {}
优雅的对象转换解决方案-MapStruct使用进阶(二)
多入参的场景
@Mapping(source = "person.description", target = "description")
@Mapping(source = "hn", target = "houseNumber")
DeliveryAddress personAndAddressToDeliveryAddressDto(Person person, Integer hn);
在多对一转换时, 遵循以下几个原则
1,当多个对象中, 有其中一个为 null, 则会直接返回 null
2,如一对一转换一样, 属性通过名字来自动匹配。 因此, 名称和类型相同的不需要进行特殊处理
3,当多个原对象中,有相同名字的属性时,需要通过 @Mapping 注解来具体的指定, 以免出现歧义(不指定会报错)。 如上面的 description
https://blog.csdn.net/qq_35211818/article/details/104714363
核心代码
@Mapper
public interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mappings({
// 如果target与source中的字段同名,可以不用写,会自动处理
@Mapping(source = "address", target = "address"),
// 非同名字段需要显示来写
@Mapping(source = "birthday", target = "birth"),
// 日期类可以指定映射格式
@Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
// 使用表达式进行格式处理
@Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
// 忽视该部分的转换,维持target中的原值
@Mapping(target = "email", ignore = true),
// 简单对象可以直接通过字段.字段获取
@Mapping(source = "area.province", target = "province"),
// 复杂对象mapping通过expression,获取封装在DTO对象里的list字段里的值
@Mapping(expression = "java(companyVerifyInfoDTO.contactInfo.website.get(0).getWebsite())", target = "website"),
// 通过表达式进行处理,如果source为null,映射为空字符串
@Mapping(expression = "java(branchDTO.operName == null? joptsimple.internal.Strings.EMPTY : branchDTO.operName)", target = "oper_name"),
})
PersonDTO domain2dto(Person person);
// List之间的转换建立在上述实体的转换的基础上,必须有上述domain2dto,否则无法实现List之间的转换。
List<PersonDTO> domain2dto(List<Person> people);
}
该接口的实现类通过lombok自动生成,点击maven项目的compile即可。
使用方法如下:
DTO convertResult = PersonConverter.INSTANCE.convert(person);
自定义注解与切面
参考:https://cloud.tencent.com/developer/article/1771768
自定义注解
import java.lang.annotation.*;
@Target({ElementType.FIELD,ElementType.TYPE}) //Target 注解的使用域,FIELD表示使用在属性上面,TYPE表示使用在类上面
@Retention(RetentionPolicy.RUNTIME) //Retention 设置注解的生命周期 ,这里定义为RetentionPolicy.RUNTIME 非常关键
@Documented
public @interface RelMapper {
//自定义属性
String value() default "";
String type() default ""; // value : status(标记属性值为Y/N的属性) / date(标记属性类型为时间)
}
自定义属性,大家可以根据自己项目中的需求增加不同的属性。
工具类方法实现
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.Date;
import org.apache.commons.lang.StringUtils;
import com.ctccbs.common.annotation.RelMapper;
public class RelationMapperUtils {
/**
* Entity and Dto Mapper
* @param entry
* @param dto
* @param enToDto
* ture : Entity To Dto (defult)
* false : Dto To Entry
* Rule:
* 实现相互转换前提: Dto field name(dto和entry的field name相同并且 类上有@RelMapper) 或 field的@RelMapper(value="Entity field name") 满足其一即可转换
* @return
* @throws Exception
*/
public static Object entryAndDtoMapper(Object entity, Object dto) throws Exception{
return EnAndDtoMapper(entity, dto,true);
}
public static Object entryAndDtoMapper(Object entity, Object dto,boolean enToDto) throws Exception{
return EnAndDtoMapper(entity, dto,false);
}
//last version
public static Object EnAndDtoMapper(Object entry, Object dto,boolean enToDto) throws Exception{
if(enToDto == true ? entry == null : dto == null){ return null;}
Class<? extends Object> entryclazz = entry.getClass(); //获取entity类
Class<? extends Object> dtoclazz = dto.getClass(); //获取dto类
boolean dtoExistAnno = dtoclazz.isAnnotationPresent(RelMapper.class); //判断类上面是否有自定义注解
Field [] dtofds = dtoclazz.getDeclaredFields(); //dto fields
Field [] entryfds = entryclazz.getDeclaredFields(); //entity fields
Method entrys[] = entryclazz.getDeclaredMethods(); //entity methods
Method dtos[] = dtoclazz.getDeclaredMethods(); //dto methods
String mName,fieldName,dtoFieldType=null,entFieldType=null,dtoMapName = null,dtoFieldName =null;Object value = null;
for(Method m : (enToDto ? dtos : entrys)) { //当 enToDto=true 此时是Entity转为Dto,遍历dto的属性
if((mName=m.getName()).startsWith("set")) { //只进set方法
fieldName = mName.toLowerCase().charAt(3) + mName.substring(4,mName.length()); //通过set方法获得dto的属性名
tohere:
for(Field fd: dtofds) {
fd.setAccessible(true); //setAccessible是启用和禁用访问安全检查的开关
if(fd.isAnnotationPresent(RelMapper.class)||dtoExistAnno){ //判断field上注解或类上面注解是否存在
//获取与Entity属性相匹配的映射值(两种情况:1.该field上注解的value值(Entity的field name 和Dto 的field name 不同) 2.该field本身(本身则是Entity的field name 和Dto 的field name 相同))
dtoMapName = fd.isAnnotationPresent(RelMapper.class) ? (fd.getAnnotation(RelMapper.class).value().toString().equals("")?fd.getName().toString():fd.getAnnotation(RelMapper.class).value().toString()):fd.getName().toString();
if(((enToDto ? fd.getName() : dtoMapName)).toString().equals(fieldName)) {
dtoFieldType = fd.getGenericType().toString().substring(fd.getGenericType().toString().lastIndexOf(".") + 1); // 获取dto属性的类型(如 private String field 结果 = String)
for(Field fe : entryfds) {
fe.setAccessible(true);
if(fe.getName().toString().equals(enToDto ? dtoMapName : fieldName) ) {//遍历Entity类的属性与dto属性注解中的value值匹配
entFieldType = fe.getGenericType().toString().substring(fe.getGenericType().toString().lastIndexOf(".") + 1); //获取Entity类属性类型
dtoFieldName = enToDto ? dtoMapName : fd.getName().toString();
break tohere;
}
}
}
}
}
if(dtoFieldName!= null && !dtoFieldName.equals("null")) {
for(Method md : (enToDto ? entrys : dtos)) {
if(md.getName().toUpperCase().equals("GET"+dtoFieldName.toUpperCase())){
dtoFieldName = null;
if(md.invoke(enToDto ? entry : dto) == null) { break;} //去空操作
//Entity类field 与Dto类field类型不一致通过TypeProcessor处理转换
value = (entFieldType.equals(dtoFieldType))? md.invoke(enToDto ? entry : dto) :TypeProcessor(entFieldType, dtoFieldType,md.invoke(enToDto ? entry : dto),enToDto ? true : false);
m.invoke(enToDto ? dto : entry, value); //得到field的值 通过invoke()赋值给要转换类的对应属性
value = null;
break;
}
}
}
}
}
return enToDto ? dto : entry;
}
//类型转换处理
public static Object TypeProcessor(String entFieldType,String dtoFieldType, Object obj,boolean enToDto) {
if(entFieldType.equals(dtoFieldType)) return obj;
if(!entFieldType.equals(dtoFieldType)) {
switch(entFieldType) {
case "Date":
return (enToDto)?TypeConverter.dateToString((Date) obj):TypeConverter.stringToDate(obj.toString());
case "Timestamp":
return TypeConverter.timestampToTimestampString((Timestamp)obj);
case "Integer":
return (enToDto) ? obj.toString() : Integer.parseInt((String)obj) ;
}
}
return obj;
}
上面EnAndDtoMapper()方法的实现是Entity和Dto之间互相转换结合在一起,enToDto = true 表示的是Entity转Dto实现,false则相反。
使用
1)Entity类 与 Dto类对应
2)调用
public static void main(String[] args) {
//Entity数据转成Dto数据集
Person person = dao.getPersonRecord();
RelationMapperUtils.entryAndDtoMapper(person,new PersonDto());
//Dto数据转成Entity数据
RelationMapperUtils.entryAndDtoMapper(new Person(),personDto,false);
}