JavaBeans映射工具比较

  • BeanUtils
  • BeanCopier
  • Orika
    • 多对一
    • List映射
  • MapStruct
  • Orika、MapStruct耗时比较

定义一些基础对象

// 定义转换的实体
public class OrikaDTO {private Integer id;private String name;private Date createTime;private Timestamp lastUpdateTime;private List<OrikaItemDTO> orikaItemDTO;private TypeEnum downloadResourceTypeEnum;private OrikaItemDTO mainOrikaItemDTO;private Boolean disable;private Float prePrice;private BigDecimal totalPrice;private Map<String, String> kv;private String oneString;private Integer oneInt;OrikaItemDTO details;
}public class OrikaItemDTO {private Double price;private Integer uint32Count;private Long uint64Count;private Integer sint32Count;private Long sint64Count;private Integer fixed32Count;private Long fixed64Count;private Integer sfixed32Count;private Long sfixed64Count;private String type;
}public class OrikaPO {private Integer id;private String name;private String createTime;private Long lastUpdateTime;private List<OrikaItemPO> orikaItem;private Integer downloadResourceTypeEnum;private OrikaItemPO mainOrikaItem;private Boolean disable;private Float prePrice;private String totalPrice;private String type;
}public class OrikaItemPO {private Double price;private Integer uint32Count;private Long uint64Count;private Integer sint32Count;private Long sint64Count;private Integer fixed32Count;private Long fixed64Count;private Integer sfixed32Count;private Long sfixed64Count;}public class ResponseA<T> {private T data;
}public class ResponseB<T> {private T data;
}

BeanUtils

通常有两种,一种是Apache工具包下的,虽然可以自定义转换,但执行效率很低,基本上不建议使用;另一种是Spring工具包下的,这种相对简单,只进行同名字段的映射,不支持自定义的转换,且只检查字段的可访问性,关键的限制在于

ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())

要求目标映射对象字段可赋值于源映射对象字段,也就是说源映射对象字段是目标映射对象字段的父类或父接口;所以字段有泛型或继承关系时可能只支持单向的映射。

BeanCopier

BeanCopier是以字节码的方法进行赋值,比起用反射方法效率上会提高,但其特性不够丰富,默认情况下仅复制完全相同的属性名和属性类型;其性能消耗在构造BeanCopier对象上,可以考虑在创建时存缓存复用或是其他提前加载的方法。

定义一些基础对象

OrikaItemDTO orikaItemDTO = new OrikaItemDTO();
orikaItemDTO.setPrice(20.2);
orikaItemDTO.setUint32Count(23);
orikaItemDTO.setUint64Count(23333L);
orikaItemDTO.setSint32Count(-11);
orikaItemDTO.setSint64Count(-111111L);
orikaItemDTO.setSfixed32Count(12);
orikaItemDTO.setSfixed64Count(333L);
orikaItemDTO.setType("SS");OrikaDTO orikaDTO = new OrikaDTO();
orikaDTO.setId(1);
orikaDTO.setName("飞");
orikaDTO.setCreateTime(new Date(System.currentTimeMillis()));
orikaDTO.setLastUpdateTime(new Timestamp(System.currentTimeMillis()));orikaDTO.setDownloadResourceTypeEnum(TypeEnum.download_resource_type_enum_picture);OrikaItemDTO orikaItemDTO1 = new OrikaItemDTO();
orikaItemDTO1.setUint32Count(23);
orikaItemDTO1.setSint64Count(-111111L);
orikaItemDTO1.setSfixed64Count(333L);orikaDTO.setMainOrikaItemDTO(orikaItemDTO1);
orikaDTO.setDisable(true);
orikaDTO.setTotalPrice(new BigDecimal("22.2"));
Map<String, String> maps = new HashMap<>();
maps.put("k1", "v1");
orikaDTO.setKv(maps);List<OrikaItemDTO> orikaItemDTOList = new ArrayList<>();
orikaItemDTOList.add(orikaItemDTO);
orikaDTO.setOrikaItemDTO(orikaItemDTOList);

测试方法

BeanCopier beanCopier = BeanCopier.create(OrikaDTO.class, OrikaPO.class, false);// 第三个参数就是使用默认的转换,只复制完全相同的属性名和属性类型
OrikaPO orikaPO = new OrikaPO();
beanCopier.copy(orikaDTO, orikaPO, null);
System.out.println(orikaPO); // OrikaPO(id=1, name=飞, createTime=null, lastUpdateTime=null, orikaItem=null, downloadResourceTypeEnum=null, mainOrikaItem=null, disable=true, prePrice=null, totalPrice=null, type=null)// 自定义转换 一旦使用了自定义转换,就要考虑所有字段的转换,而且如果有对象属性就很麻烦。
public class BeanCopierConverter implements Converter {@Overridepublic Object convert(Object source, Class target, Object targetObject) {if (source instanceof Timestamp) {Timestamp timestamp = (Timestamp) source;return timestamp.getTime();}if (source instanceof Date) {Date date = (Date) source;Timestamp timestamp = new Timestamp(date.getTime());LocalDateTime localDateTime = timestamp.toLocalDateTime();return localDateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));}return null;}
}BeanCopier beanCopier = BeanCopier.create(OrikaDTO.class, OrikaPO.class, true);// 第三个参数就是使用默认的转换,只复制完全相同的属性名和属性类型
OrikaPO orikaPO = new OrikaPO();
beanCopier.copy(orikaDTO, orikaPO, new BeanCopierConverter());
System.out.println(orikaPO); // OrikaPO(id=null, name=null, createTime=2020-08-11 11:16:54, lastUpdateTime=1597115814525, orikaItem=null, downloadResourceTypeEnum=null, mainOrikaItem=null, disable=null, prePrice=null, totalPrice=null, type=null)

对象间字段较少、类型和名称相同BeanCopier可以使用。

Orika

orika既拥有丰富的特性,底层也是以字节码的显示处理,所以效率上也不错。

需引入的jar包

<!--引入jar包-->
<dependency><groupId>ma.glasnost.orika</groupId><artifactId>orika-core</artifactId><version>1.5.4</version>
</dependency>

定义一些基础方法

// 自定义枚举转换 枚举和Integer互转
public class EnumOrikaConverter extends BidirectionalConverter<Integer, Enum> {@Overridepublic Enum convertTo(Integer source, Type<Enum> destinationType, MappingContext mappingContext) {try {if (Enum.class.isAssignableFrom(destinationType.getRawType())) {// Integer转Enumfor (Object objectEnum : destinationType.getRawType().getEnumConstants()) {BeanInfo beanInfo = Introspector.getBeanInfo(objectEnum.getClass());for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {Object getObject = propertyDescriptor.getReadMethod().invoke(objectEnum);if (getObject instanceof Integer && source.equals(getObject)) {return (Enum) objectEnum;}}}}} catch (Exception e) {e.printStackTrace();}return null;}@Overridepublic Integer convertFrom(Enum source, Type<Integer> destinationType, MappingContext mappingContext) {try {BeanInfo beanInfo = Introspector.getBeanInfo(source.getClass());for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {Object getObject = propertyDescriptor.getReadMethod().invoke(source);if (getObject instanceof Integer) {return (Integer) getObject;}}} catch (Exception e) {e.printStackTrace();}return null;}
}// 自定义Date转换 Date和String互转
public class DateOrikaConverter extends BidirectionalConverter<String, Date> {private String format;public DateOrikaConverter(String format) {this.format = format;}@Overridepublic Date convertTo(String source, Type<Date> destinationType, MappingContext mappingContext) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDateTime localDateTime = LocalDateTime.parse(source, dateTimeFormatter);ZoneId zoneId = ZoneId.systemDefault();ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);return Date.from(zonedDateTime.toInstant());}@Overridepublic String convertFrom(Date source, Type<String> destinationType, MappingContext mappingContext) {Timestamp timestamp = new Timestamp(source.getTime());LocalDateTime localDateTime = timestamp.toLocalDateTime();return localDateTime.format(DateTimeFormatter.ofPattern(format));}
}private OrikaDTO buildOrikaDTO() {OrikaItemDTO orikaItemDTO = new OrikaItemDTO();orikaItemDTO.setPrice(20.2);orikaItemDTO.setUint32Count(23);orikaItemDTO.setUint64Count(23333L);orikaItemDTO.setSint32Count(-11);orikaItemDTO.setSint64Count(-111111L);orikaItemDTO.setSfixed32Count(12);orikaItemDTO.setSfixed64Count(333L);orikaItemDTO.setType("SS");OrikaDTO orikaDTO = new OrikaDTO();orikaDTO.setName("飞");orikaDTO.setCreateTime(new Date(System.currentTimeMillis()));orikaDTO.setLastUpdateTime(new Timestamp(System.currentTimeMillis()));orikaDTO.setDownloadResourceTypeEnum(TypeEnum.download_resource_type_enum_picture);OrikaItemDTO orikaItemDTO1 = new OrikaItemDTO();orikaItemDTO1.setUint32Count(23);orikaItemDTO1.setSint64Count(-111111L);orikaItemDTO1.setSfixed64Count(333L);orikaDTO.setMainOrikaItem(orikaItemDTO1);orikaDTO.setDisable(true);orikaDTO.setTotalPrice(new BigDecimal("22.2"));Map<String, String> maps = new HashMap<>();maps.put("k1", "v1");orikaDTO.setKv(maps);List<OrikaItemDTO> orikaItemDTOList = new ArrayList<>();orikaItemDTOList.add(orikaItemDTO);orikaDTO.setOrikaItem(orikaItemDTOList);return orikaDTO;
}// 自定义Timestamp转换 Timestamp和String互转
public class TimestampOrikaConverter extends BidirectionalConverter<String, Timestamp> {private String format;public TimestampOrikaConverter(String format) {this.format = format;}@Overridepublic Timestamp convertTo(String source, Type<Timestamp> destinationType, MappingContext mappingContext) {DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(format);LocalDateTime localDateTime = LocalDateTime.parse(source, dateTimeFormatter);ZoneId zoneId = ZoneId.systemDefault();ZonedDateTime zonedDateTime = localDateTime.atZone(zoneId);Date date = Date.from(zonedDateTime.toInstant());return new Timestamp(date.getTime());}@Overridepublic String convertFrom(Timestamp source, Type<String> destinationType, MappingContext mappingContext) {LocalDateTime localDateTime = source.toLocalDateTime();return localDateTime.format(DateTimeFormatter.ofPattern(format));}
}public class EnumMapper extends CustomMapper<OrikaDTO, OrikaPO> {@Overridepublic void mapAtoB(OrikaDTO orikaDTO, OrikaPO orikaPO, MappingContext context) {orikaPO.setDownloadResourceTypeEnum(orikaDTO.getDownloadResourceTypeEnum().getCode());}@Overridepublic void mapBtoA(OrikaPO orikaPO, OrikaDTO orikaDTO, MappingContext context) {orikaDTO.setDownloadResourceTypeEnum(TypeEnum.getTypeByCode(orikaPO.getDownloadResourceTypeEnum()));}
}public class OrikaPOFactory implements ObjectFactory<OrikaPO> {@Overridepublic OrikaPO create(Object source, MappingContext mappingContext) {OrikaPO orikaPO = new OrikaPO();orikaPO.setId(111111);return orikaPO;}
}

测试方法

/*转换的具体方法*/
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
// mapperFactory.registerObjectFactory(new OrikaPOFactory(), TypeFactory.valueOf(OrikaPO.class)); // 自定义对象工厂 可用于创建目标对象的默认对象 但是和.byDefault()有冲突,不使用.byDefault(),转换出的orikaPO对象都会有111111的id,使用.byDefault()会被覆盖 不推荐使用
// 自定义转换器 指定全局转换器必须是明确的类型 不能是泛型或子类型。比如枚举若要设置成全局转换器,必须指定具体枚举类,字段级别的则可以设置成Enum类型
ConverterFactory converterFactory = mapperFactory.getConverterFactory();
// 注册枚举转换器
converterFactory.registerConverter("enumOrikaConverter", new EnumOrikaConverter());
// 注册全局转换器 默认转换的时间是单向转换,只能从Date/Timestamp转成String,默认的双向映射是Long的时间戳  可选
converterFactory.registerConverter(new DateOrikaConverter("yyyy-MM-dd HH:ss:mm"));
converterFactory.registerConverter(new TimestampOrikaConverter("yyyy-MM-dd HH:ss:mm"));
mapperFactory.classMap(OrikaDTO.class, OrikaPO.class)// 自定义映射字段 可选 orikaItem{price}这种方式可以取list中泛型对象中的某一字段.field("orikaItem[0].type", "type")// 默认的枚举转换是转成枚举值字段字符串 注册字段级别的转换器 可选.fieldMap("downloadResourceTypeEnum", "downloadResourceTypeEnum").converter("enumOrikaConverter").add()//  其他字段以默认的方式转换 不使用这个方法,需要手动映射每个字段,建议使用,不然很麻烦.byDefault()//.customize(new EnumMapper()) // 自定义映射器和.byDefault()可能会有冲突,比如这里的enum,而且自定义映射器通常也只是对两个具体的类进行自定义映射,可以和正常get/set方式一样分别处理每个字段。.register();
BoundMapperFacade<OrikaDTO, OrikaPO> mapperFacade = mapperFactory.getMapperFacade(OrikaDTO.class, OrikaPO.class);
OrikaPO orikaPO = mapperFacade.map(orikaDTO); // 源对象转目标对象 OrikaPO(id=null, name=飞, createTime=2020-08-24 16:37:27, lastUpdateTime=1598257657318, orikaItem=[OrikaItemPO(price=20.2, uint32Count=23, uint64Count=23333, sint32Count=-11, sint64Count=-111111, fixed32Count=null, fixed64Count=null, sfixed32Count=12, sfixed64Count=333)], downloadResourceTypeEnum=2, mainOrikaItem=OrikaItemPO(price=null, uint32Count=23, uint64Count=null, sint32Count=null, sint64Count=-111111, fixed32Count=null, fixed64Count=null, sfixed32Count=null, sfixed64Count=333), disable=true, prePrice=null, totalPrice=22.2, type=SS)
OrikaDTO orikaDTOReverse = mapperFacade.mapReverse(orikaPO);    // 目标对象转源对象 OrikaDTO(id=null, name=飞, createTime=Mon Aug 24 16:27:37 CST 2020, lastUpdateTime=2020-08-24 16:27:37.318, orikaItem=[OrikaItemDTO(price=20.2, uint32Count=23, uint64Count=23333, sint32Count=-11, sint64Count=-111111, fixed32Count=null, fixed64Count=null, sfixed32Count=12, sfixed64Count=333, type=null)], downloadResourceTypeEnum=download_resource_type_enum_picture, mainOrikaItem=OrikaItemDTO(price=null, uint32Count=23, uint64Count=null, sint32Count=null, sint64Count=-111111, fixed32Count=null, fixed64Count=null, sfixed32Count=null, sfixed64Count=333, type=null), disable=true, prePrice=null, totalPrice=22.2, kv=null, oneString=null, oneInt=null, details=null)

Orika还可以对泛型进行转换。使用Type对具体的类型进行转换。

ResponseA<OrikaItemDTO> responseA = new ResponseA<>();
OrikaItemDTO orikaItemDTO = new OrikaItemDTO();
orikaItemDTO.setPrice(12.2);
orikaItemDTO.setFixed32Count(12);
responseA.setData(orikaItemDTO);
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
Type<ResponseA<OrikaItemDTO>> responseAType = new TypeBuilder<ResponseA<OrikaItemDTO>>() {}.build();
Type<ResponseB<OrikaItemPO>> responseBType = new TypeBuilder<ResponseB<OrikaItemPO>>() {}.build();
BoundMapperFacade<ResponseA<OrikaItemDTO>, ResponseB<OrikaItemPO>> mapperFacade = mapperFactory.getMapperFacade(responseAType, responseBType);
ResponseB<OrikaItemPO> responseB = mapperFacade.map(responseA); // ResponseB(data=OrikaItemPO(price=12.2, uint32Count=null, uint64Count=null, sint32Count=null, sint64Count=null, fixed32Count=12, fixed64Count=null, sfixed32Count=null, sfixed64Count=null))
ResponseA<OrikaItemDTO> responseAReverse = mapperFacade.mapReverse(responseB); // ResponseA(data=OrikaItemDTO(price=12.2, uint32Count=null, uint64Count=null, sint32Count=null, sint64Count=null, fixed32Count=12, fixed64Count=null, sfixed32Count=null, sfixed64Count=null, type=null))

多对一

一般还会碰到多个bean要映射一个bean的情况。

定义一些基础对象

@Data
public class OrikaItemDTO {private Double price;private Integer uint32Count;private Long uint64Count;private Integer sint32Count;private Long sint64Count;private Integer fixed32Count;
}@Data
public class OrikaItem2DTO {private Long fixed64Count;private Integer sfixed32Count;private Long sfixed64Count;private String type;
}

创建多个mapperFacade就可以了。

OrikaItemDTO orikaItemDTO = new OrikaItemDTO();
orikaItemDTO.setPrice(12.2);
orikaItemDTO.setFixed32Count(12);
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();BoundMapperFacade<OrikaItemDTO, OrikaItemPO> mapperFacade = mapperFactory.getMapperFacade(OrikaItemDTO.class, OrikaItemPO.class);
OrikaItemPO orikaItemPO = mapperFacade.map(orikaItemDTO);OrikaItem2DTO orikaItem2DTO = new OrikaItem2DTO();
orikaItem2DTO.setFixed64Count(1222L);
BoundMapperFacade<OrikaItem2DTO, OrikaItemPO> mapperFacade2 = mapperFactory.getMapperFacade(OrikaItem2DTO.class, OrikaItemPO.class);
orikaItemPO = mapperFacade2.map(orikaItem2DTO, orikaItemPO); // OrikaItemPO(price=12.2, uint32Count=null, uint64Count=null, sint32Count=null, sint64Count=null, fixed32Count=12, fixed64Count=1222, sfixed32Count=null, sfixed64Count=null)OrikaItemDTO orikaItemDTOReverse = mapperFacade.mapReverse(orikaItemPO); // OrikaItemDTO(price=12.2, uint32Count=null, uint64Count=null, sint32Count=null, sint64Count=null, fixed32Count=12)
OrikaItem2DTO orikaItem2DTOReverse = mapperFacade2.mapReverse(orikaItemPO); // OrikaItem2DTO(fixed64Count=1222, sfixed32Count=null, sfixed64Count=null, type=null)

以上基本能满足业务详情,Orika还有其他功能可以参考链接Orika文档

List映射

定义一些基础对象

@Data
@ToString
public class BaseResult<T> implements Serializable {private static final long serialVersionUID = -2330327498128012094L;/*** 状态码*/private Integer code;/*** 信息*/private String message;/*** 异常信息*/private Exception exception;/*** 页码*/private Integer page;/*** 当前页数据个数*/private Integer total;/*** 总个数*/private Integer allTotal;/*** 数据*/private List<T> data;/*** 所有数据*/private List<T> allData;}@Data
@ToString
public class BaseResultDTO<T> implements Serializable {private static final long serialVersionUID = -2330327498128012094L;/*** 状态码*/private Integer code;/*** 信息*/private String message;/*** 异常信息*/private Exception exception;/*** 页码*/private Integer page;/*** 当前页数据个数*/private Integer total;/*** 总个数*/private Integer allTotal;/*** 数据*/private List<T> records;/*** 所有数据*/private List<T> allData;}@Data
@EqualsAndHashCode(callSuper = false)
public class Share implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/private Long id;/*** 创建时间*/private LocalDateTime createTime;/*** 编辑时间*/private LocalDateTime updateTime;/*** 是否可用*/private Boolean valid;/*** 创建用户*/private Long createUser;/*** 标题*/private String shareTitle;/*** 内容*/private String content;/*** 话题*/private Long topic;/*** 回复数*/private Long replyCount;/*** 点赞数*/private Long thumbCount;/*** 收藏数*/private Long collectCount;}@Data
@ToString
public class BaseDTO implements Serializable {private static final long serialVersionUID = -2330327498128012094L;/*** id*/private Integer id;/*** 创建时间*/private Timestamp createTime;/*** 创建时间字符串*/private String createTimeString;/*** 版本*/private Integer version;/*** sessionId*/private String sessionId;
}

测试方法

/* mapAsList */
List<Share> shares = new ArrayList<>();
// 省略设值
mapperFactory.classMap(Share.class, ShareDTO.class).fieldMap("createTime", "createTimeString").converter("localDateTimeConverter").add().register();
MapperFacade mapperFacade = mapperFactory.getMapperFacade();
baseResult.setData(mapperFacade.mapAsList(shares, ShareDTO.class));
// 由于推荐使用BoundMapperFacade,上面的方法可以用循环转换list中每一项的方式/* 对像中有list字段转换 递归定义两个classMap*/
IPage<Share> shareIPage = shareService.page(page, lambdaQueryWrapper); // 这里是设值Type<BaseResultDTO<Share>> iPageType = new TypeBuilder<IPage<Share>>() {}.build();
Type<BaseResult<ShareDTO>> baseResultType = new TypeBuilder<BaseResult<ShareDTO>>() {}.build();
mapperFactory.classMap(iPageType, baseResultType).field("records", "data").byDefault().register();
mapperFactory.classMap(Share.class, ShareDTO.class).fieldMap("createTime", "createTimeString").converter("localDateTimeConverter").add().byDefault().register();
BoundMapperFacade<IPage<Share>, BaseResult<ShareDTO>> mapperFacade = mapperFactory.getMapperFacade(iPageType, baseResultType);
baseResult = mapperFacade.map(shareIPage);

建议MapperFactory仅初始化一次,全局维护一个单例的(MapperFactory和mapperFacade都是线程安全的);使用Type<?> class时,注意构建其会耗时,可以考虑缓存复用或提前构建。

MapStruct

详细介绍参考MapStruct文档

Orika、MapStruct耗时比较

定义一些基础对象

@Data
@ToString
public class BaseResult<T> implements Serializable {private static final long serialVersionUID = -2330327498128012094L;/*** 状态码*/private Integer code;/*** 信息*/private String message;/*** 异常信息*/private Exception exception;/*** 页码*/private Integer page;/*** 当前页数据个数*/private Integer total;/*** 总个数*/private Integer allTotal;/*** 数据*/private List<T> data;/*** 所有数据*/private List<T> allData;
}@Data
@ToString
public class BaseDTO implements Serializable {private static final long serialVersionUID = -2330327498128012094L;/*** id*/private Integer id;/*** 是否可用*/private Boolean valid;/*** 创建用户*/private Long createUser;/*** 创建时间*/private Timestamp createTime;/*** 创建时间字符串*/private String createTimeString;/*** 版本*/private Integer version;/*** sessionId*/private String sessionId;
}@EqualsAndHashCode(callSuper = true)
@Data
@ToString
public class ShareDTO extends BaseDTO implements Serializable {private static final long serialVersionUID = -1140776438655059071L;/*** 标题*/private String shareTitle;/*** 内容*/private String content;/*** 话题*/private Long topic;/*** 回复数*/private Long replyCount;/*** 点赞数*/private Long thumbCount;/*** 收藏数*/private Long collectCount;/*** 用户名*/private String userName;/*** 用户类型 1家长 2老师 3官方*/private Integer identityType;/*** 年级*/private Integer grade;/*** 头像链接*/private String avatar;/*** 城市*/private Integer city;/*** 粉丝数*/private Long fan;/*** 关注数*/private Long identityFollow;/*** 获赞数*/private Long thumbed;/*** 链接*/List<LinkDTO> links;
}@EqualsAndHashCode(callSuper = true)
@Data
@ToString
public class LinkDTO extends BaseDTO implements Serializable {private static final long serialVersionUID = -1140776438655059071L;/*** 链接地址*/private String url;/*** 类型 1图片 2视频*/private Integer linkType;/*** 分享*/private Long share;/*** 链接标题*/private String linkTitle;}@Data
@EqualsAndHashCode(callSuper = false)
@TableName("s_share")
public class Share implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 创建时间*/private LocalDateTime createTime;/*** 编辑时间*/private LocalDateTime updateTime;/*** 是否可用*/private Boolean valid;/*** 创建用户*/private Long createUser;/*** 标题*/private String shareTitle;/*** 内容*/private String content;/*** 话题*/private Long topic;/*** 回复数*/private Long replyCount;/*** 点赞数*/private Long thumbCount;/*** 收藏数*/private Long collectCount;}@Data
@EqualsAndHashCode(callSuper = false)
@TableName("s_link")
public class Link implements Serializable {private static final long serialVersionUID = 1L;/*** 主键*/@TableId(value = "id", type = IdType.AUTO)private Long id;/*** 创建时间*/private LocalDateTime createTime;/*** 编辑时间*/private LocalDateTime updateTime;/*** 是否可用*/private Boolean valid;/*** 创建用户*/private Long createUser;/*** 链接地址*/private String url;/*** 类型 1图片 2视频*/private Integer linkType;/*** 分享*/private Long share;/*** 链接标题*/private String linkTitle;}public class MyDateUtils {public static String formatAppTime(LocalDateTime source) {Timestamp timestamp = Timestamp.valueOf(source);Date now = new Date();long compare = now.getTime() - timestamp.getTime();if (compare < 60000) {return "刚刚";} else {long minuteResidue = compare % 60000;long minute = compare / 60000;if (minuteResidue > 0 && minute < 60) {return minute + "分钟前";} else {long hourResidue = compare % 3600000;long hour = compare / 3600000;if (hourResidue > 0 && hour < 24) {return hour + "小时前";}}}Timestamp timestampDayZero = MyDateUtils.getZeroTimestampNow(timestamp);Timestamp currentDayZero = MyDateUtils.getZeroTimestampNow(new Timestamp(now.getTime()));if (currentDayZero.compareTo(timestampDayZero) == 0) {return MyDateUtils.formatTimestamp(timestamp, "MM-dd");} else {return MyDateUtils.formatTimestamp(timestamp, "MM-dd-yyyy");}}
}...public class LocalDateTimeConverter extends BidirectionalConverter<LocalDateTime, String> {@Overridepublic String convertTo(LocalDateTime source, Type<String> destinationType, MappingContext mappingContext) {return MyDateUtils.formatAppTime(source);}@Overridepublic LocalDateTime convertFrom(String source, Type<LocalDateTime> destinationType, MappingContext mappingContext) {return null;}
}public class GradeConverter extends BidirectionalConverter<GradeEnum, String> {@Overridepublic String convertTo(GradeEnum source, Type<String> destinationType, MappingContext mappingContext) {return source.getDesc();}@Overridepublic GradeEnum convertFrom(String source, Type<GradeEnum> destinationType, MappingContext mappingContext) {for (GradeEnum gradeEnum :  GradeEnum.values()) {if (gradeEnum.getDesc().equals(source)) {return gradeEnum;}}return null;}
}public class IdentityTypeConverter extends BidirectionalConverter<IdentityTypeEnum, String> {@Overridepublic String convertTo(IdentityTypeEnum source, Type<String> destinationType, MappingContext mappingContext) {return source.getDesc();}@Overridepublic IdentityTypeEnum convertFrom(String source, Type<IdentityTypeEnum> destinationType, MappingContext mappingContext) {for (IdentityTypeEnum identityTypeEnum : IdentityTypeEnum.values()) {if (identityTypeEnum.getDesc().equals(source)) {return identityTypeEnum;}}return null;}
}public class CityConverter extends BidirectionalConverter<CityEnum, String> {@Overridepublic String convertTo(CityEnum source, Type<String> destinationType, MappingContext mappingContext) {return source.getDesc();}@Overridepublic CityEnum convertFrom(String source, Type<CityEnum> destinationType, MappingContext mappingContext) {for (CityEnum cityEnum : CityEnum.values()) {if (cityEnum.getDesc().equals(source)) {return cityEnum;}}return null;}
}@Component
@Order(1)
@Slf4j
@SuppressWarnings("rawtypes")
public class OrikaInit implements CommandLineRunner {public static MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();public static ConcurrentHashMap<String, Type> typeMap = new ConcurrentHashMap<>();@Overridepublic void run(String... args) {ConverterFactory converterFactory = mapperFactory.getConverterFactory();converterFactory.registerConverter("localDateTimeConverter", new LocalDateTimeConverter());converterFactory.registerConverter("gradeConverter", new GradeConverter());converterFactory.registerConverter("identityTypeConverter", new IdentityTypeConverter());converterFactory.registerConverter("cityConverter", new CityConverter());Type<IPage<Share>> iPageType = new TypeBuilder<IPage<Share>>() {}.build();Type<BaseResult<ShareDTO>> baseResultType = new TypeBuilder<BaseResult<ShareDTO>>() {}.build();Type<IPage<Reply>> iPageTypeReply = new TypeBuilder<IPage<Reply>>() {}.build();Type<BaseResult<ReplyDTO>> baseResultTypeReply = new TypeBuilder<BaseResult<ReplyDTO>>() {}.build();typeMap.put("iPageType", iPageType);typeMap.put("baseResultType", baseResultType);typeMap.put("iPageTypeReply", iPageTypeReply);typeMap.put("baseResultTypeReply", baseResultTypeReply);mapperFactory.classMap(iPageType, baseResultType).field("records", "data").byDefault().register();mapperFactory.classMap(iPageTypeReply, baseResultTypeReply).field("records", "data").byDefault().register();mapperFactory.classMap(Share.class, ShareDTO.class).fieldMap("createTime", "createTimeString").converter("localDateTimeConverter").add().byDefault().register();mapperFactory.classMap(Reply.class, ReplyDTO.class).fieldMap("createTime", "createTimeString").converter("localDateTimeConverter").add().byDefault().register();mapperFactory.classMap(Link.class, LinkDTO.class).exclude("id").exclude("createTime").exclude("updateTime").exclude("createUser").exclude("valid").byDefault().register();mapperFactory.classMap(Identity.class, ShareDTO.class).exclude("id").exclude("createTime").exclude("updateTime").exclude("createUser").exclude("valid").fieldMap("grade", "grade").converter("gradeConverter").add().fieldMap("identityType", "identityType").converter("identityTypeConverter").add().fieldMap("city", "city").converter("cityConverter").add().byDefault().register();mapperFactory.classMap(Identity.class, ReplyDTO.class).exclude("id").exclude("createTime").exclude("updateTime").exclude("createUser").exclude("valid").fieldMap("grade", "grade").converter("gradeConverter").add().fieldMap("identityType", "identityType").converter("identityTypeConverter").add().fieldMap("city", "city").converter("cityConverter").add().byDefault().register();mapperFactory.classMap(Topic.class, ShareDTO.class).exclude("id").exclude("createTime").exclude("updateTime").exclude("createUser").exclude("valid").byDefault().register();mapperFactory.getMapperFacade(iPageType, baseResultType);mapperFactory.getMapperFacade(iPageTypeReply, baseResultTypeReply);mapperFactory.getMapperFacade(Link.class, LinkDTO.class);mapperFactory.getMapperFacade(Identity.class, ShareDTO.class);mapperFactory.getMapperFacade(Identity.class, ReplyDTO.class);mapperFactory.getMapperFacade(Topic.class, ShareDTO.class);}
}...@Mapper(imports = {MyDateUtils.class})
public interface MapStructShareMapper {@Mappings({@Mapping(target = "id", ignore = true),@Mapping(target = "createTime", ignore = true),@Mapping(target = "createUser", ignore = true),@Mapping(target = "createTimeString", ignore = true),@Mapping(target = "valid", ignore = true),})LinkDTO linkTODTO(Link link);@Mappings({@Mapping(target = "createTimeString", expression = "java(MyDateUtils.formatAppTime(share.getCreateTime()))"),@Mapping(target = "createTime", ignore = true)})ShareDTO shareTODTO(Share share);List<ShareDTO> shareTODTOS(List<Share> shares);@Mappings({@Mapping(target = "id", ignore = true),@Mapping(target = "createTime", ignore = true),@Mapping(target = "createUser", ignore = true),@Mapping(target = "valid", ignore = true),})void topicUpdateShareDTO(Topic topic, @MappingTarget ShareDTO shareDTO);@Mappings({@Mapping(target = "id", ignore = true),@Mapping(target = "createTime", ignore = true),@Mapping(target = "createUser", ignore = true),@Mapping(target = "valid", ignore = true),@Mapping(target = "grade", expression = "java(identity.getGrade().getDesc())"),@Mapping(target = "city", expression = "java(identity.getCity().getDesc())"),@Mapping(target = "identityType", expression = "java(identity.getIdentityType().getDesc())"),})void identityUpdateShareDTO(Identity identity, @MappingTarget ShareDTO shareDTO);}@Slf4j
public class ShareManagerImpl implements ShareManager {@Resourceprivate IShareService shareService;@Resourceprivate IIdentityService identityService;@Resourceprivate ILinkService linkService;@Overridepublic BaseResult<ShareDTO> getList(BaseResponse baseResponse) {BaseResult<ShareDTO> baseResult;IPage<Share> page = new Page<>(baseResponse.getPage(), baseResponse.getSize());LambdaQueryWrapper<Share> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(Share::getValid, true).orderByDesc(Share::getCreateTime);IPage<Share> shareIPage = shareService.page(page, lambdaQueryWrapper);long startByOrika = System.currentTimeMillis();log.info("orika start {}", startByOrika);baseResult = this.setShareDetailByOrika(shareIPage);log.info("orika end {}", System.currentTimeMillis() - startByOrika);long startByBeanMapStruct = System.currentTimeMillis();baseResult = this.setShareDetailByMapStruct(shareIPage);log.info("MapStruct end {}", System.currentTimeMillis() - startByBeanMapStruct);baseResult.setCode(CodeMessageEnum.成功.getValue());baseResult.setPage(baseResponse.getPage());baseResult.setAllTotal((int) shareIPage.getTotal());return baseResult;}private BaseResult<ShareDTO> setShareDetailByOrika(IPage<Share> shareIPage) {BaseResult<ShareDTO> baseResult = new BaseResult<>();List<Share> shares = shareIPage.getRecords();if (!CollectionUtils.isEmpty(shares)) {// 转换“分享”对象Type<IPage<Share>> iPageTypeReply = OrikaInit.typeMap.get("iPageType");Type<BaseResult<ShareDTO>> baseResultTypeReply = OrikaInit.typeMap.get("baseResultType");BoundMapperFacade<IPage<Share>, BaseResult<ShareDTO>> mapperFacadeShare =OrikaInit.mapperFactory.getMapperFacade(iPageTypeReply, baseResultTypeReply);baseResult = mapperFacadeShare.map(shareIPage);List<ShareDTO> resultData = baseResult.getData();// 查询“话题”对象List<Topic> topics = topicService.listByIds(shares.stream().map(Share::getTopic).collect(Collectors.toList()));// 查找“用户”对象List<Long> userIds = shares.stream().map(Share::getCreateUser).collect(Collectors.toList());List<Identity> identities = identityService.listByIds(userIds);// 查找“链接”对象List<Long> shareIds = shares.stream().map(Share::getId).collect(Collectors.toList());LambdaQueryWrapper<Link> lambdaQueryWrapperLink = new LambdaQueryWrapper<>();lambdaQueryWrapperLink.in(Link::getShare, shareIds).eq(Link::getValid, true);List<Link> linkList = linkService.list(lambdaQueryWrapperLink);BoundMapperFacade<Link, LinkDTO> mapperFacadeLink =  OrikaInit.mapperFactory.getMapperFacade(Link.class, LinkDTO.class);BoundMapperFacade<Identity, ShareDTO> mapperFacadeUser =OrikaInit.mapperFactory.getMapperFacade(Identity.class, ShareDTO.class);BoundMapperFacade<Topic, ShareDTO> mapperFacadeTopic = OrikaInit.mapperFactory.getMapperFacade(Topic.class, ShareDTO.class);resultData.forEach(result -> {// 转换“链接”对象List<LinkDTO> linkDTOList = new ArrayList<>();linkList.forEach(link -> {if (link.getShare().intValue() == result.getId()) {linkDTOList.add(mapperFacadeLink.map(link));}});result.setLinks(linkDTOList);topics.forEach(topic -> {if (result.getTopic().equals(topic.getId())) {mapperFacadeTopic.map(topic, result);}});// 转换“用户”对象identities.forEach(identity -> {if (result.getCreateUser().equals(identity.getId())) {mapperFacadeUser.map(identity, result);}});});}return baseResult;}private BaseResult<ShareDTO> setShareDetailByMapStruct(IPage<Share> shareIPage) {BaseResult<ShareDTO> baseResult = new BaseResult<>();List<Share> shares = shareIPage.getRecords();MapStructShareMapper mapper = Mappers.getMapper(MapStructShareMapper.class);baseResult.setData(mapper.shareTODTOS(shares));// 查询“话题”对象List<Topic> topics = topicService.listByIds(shares.stream().map(Share::getTopic).collect(Collectors.toList()));// 查找“用户”对象List<Long> userIds = shares.stream().map(Share::getCreateUser).collect(Collectors.toList());List<Identity> identities = identityService.listByIds(userIds);// 查找“链接”对象List<Long> shareIds = shares.stream().map(Share::getId).collect(Collectors.toList());LambdaQueryWrapper<Link> lambdaQueryWrapperLink = new LambdaQueryWrapper<>();lambdaQueryWrapperLink.in(Link::getShare, shareIds).eq(Link::getValid, true);List<Link> linkList = linkService.list(lambdaQueryWrapperLink);baseResult.getData().forEach(result -> {// 转换“链接”对象List<LinkDTO> linkDTOList = new ArrayList<>();linkList.forEach(link -> {if (link.getShare().intValue() == result.getId()) {linkDTOList.add(mapper.linkTODTO(link));}});result.setLinks(linkDTOList);topics.forEach(topic -> {if (result.getTopic().equals(topic.getId())) {mapper.topicUpdateShareDTO(topic, result);}});// 转换“用户”对象identities.forEach(identity -> {if (result.getCreateUser().equals(identity.getId())) {mapper.identityUpdateShareDTO(identity, result);}});});return baseResult;}
}

比较首次1条数据

比较首次100条数据

比较首次1000条数据

比较非首次1条数据

比较非首次100条数据

可以看出orika首次执行随着数据增多耗时逐渐增加,但因为本身的缓存功能,之后的处理速度和mapstruct差的不多。前提是提前构建,正如上文所述,这种方式的缺点在于增加的编码的复杂度、代码的冗余和增加了启动耗时;mapstruct则相对简单点。

各种映射工具性能比较

JavaBeans映射工具比较相关推荐

  1. Touch 方法amp;属性 映射工具

    Touch 方法&属性 映射工具(0.5 版本) 标签 : github 线上后门与接口调试: 原先需要测试一个接口(如Dubbo.DAO), 或为线上留后门, 需要写大量的Web层(Api. ...

  2. 推荐一个 Java 实体映射工具 MapStruct

    声明: 1.DO(业务实体对象),DTO(数据传输对象). 2.我的代码中用到了 Lombok ,不了解的可以自行了解一下,了解的忽略这条就好. 在一个成熟的工程中,尤其是现在的分布式系统中,应用与应 ...

  3. mybatis date类型映射_MapStruct 映射工具

    MapStruct 映射工具 本篇主要讲解MapStruct 一款映射工具,只需简单的定义一个Mapper接口,在编译期间,MapStruct将生成此接口的实现,据说MapStruct性能最高是Spr ...

  4. Java实体映射工具MapStruct

    声明: 1.DO(业务实体对象),DTO(数据传输对象). 2.我的代码中用到了 Lombok ,不了解的可以自行了解一下,了解的忽略这条就好. 1 2 3 在一个成熟的工程中,尤其是现在的分布式系统 ...

  5. Java微信公众号开发-外网映射工具配置 - 星星满天 - 博客园

    一.开发环境准备 1.一个微信公众号 2.外网映射工具(开发调试)如花生壳.ngrok工具 注:与微信对接的URL要具备以下条件a:在公网上能够访问 b:端口只支持80端口 这里使用ngrok.cc: ...

  6. apple mac 下使用机械键盘的办法,键盘映射工具软件,apple mac Mechanical keyboard

    apple mac 下使用机械键盘的办法,键盘映射工具软件,apple mac Mechanical keyboard 想在苹果电脑 mac 系统下使用 机械键盘,大部分机械键盘不是为mac设计的,所 ...

  7. [NHibernate] NHibernate对象关系映射工具了解

    NHibernate是把Java的Hibernate核心部分移植到Microsoft .NET Framework上.它是一个对象关系映射工具,其目标是把.NET对象持久化到关系数据库. NHiber ...

  8. java orika_常见Bean映射工具分析评测及Orika介绍

    Bean映射工具选择 工作中,我们经常需要将对象转换成不同的形式以适应不同的api,或者在不同业务层中传输对象而不同分层的对象存在不同的格式,因此我们需要编写映射代码将对象中的属性值从一种类型转换成另 ...

  9. Science发布基因组比对革新技术:泛基因组学映射工具Giraffe

    目前,基因测序普遍使用的DNA测序仪主要基于短读长测序技术,在获得基因组序列片段后,将其映射到参考基因组序列中来确定染色体位置,识别出其与基因组参照的差异.但完全依赖单一参考序列来鉴别具有遗传多样性的 ...

最新文章

  1. DevOps笔记-02:DevOps与微服务之间是什么关系?
  2. Tomcat虚拟主机
  3. Boost:宏BOOST_ASSERT的使用实例
  4. [PAT乙级]1036 跟奥巴马一起编程
  5. 第四十五期:万亿级日访问量下,Redis在微博的9年优化历程
  6. socket编程初级
  7. 聊聊领域分析与业务建模
  8. Arcgis for Android解决中文字体乱码的问题
  9. getAttribute、setAttribute、removeAttribute
  10. Java使用FTP上传文件被损坏的问题
  11. 大数据工程师简历_大数据工程师简历专业技能怎么写
  12. PLC程序的组成结构
  13. java excel 转 图片_Java中excel转换为jpg/png图片 采用aspose-cells-18.6.jar
  14. Programer Cat 福利
  15. RQ dashboard使用
  16. 学习经历感悟——基础 > 语言
  17. 重生之我又是蝌蚪(召唤神龙)源码和无敌版
  18. plg.Polygon
  19. ngro_k服务器搭建(本地电脑与微信交互)
  20. C++保留小数点后两位

热门文章

  1. ios13怎么打开科学计算机,Apple支援:iOS 13 分屏如何操作?iOS 13 分屏显示在哪设置开启?...
  2. 自媒体怎么做,才能在短时间内变现赚钱?
  3. 数字图像处理及MATLAB实现实验二——数字图像处理基础
  4. 第五章 ArcGIS数据编辑
  5. 2022年化工自动化控制仪表最新解析及化工自动化控制仪表证考试
  6. WinXP DDK下载
  7. 豆瓣 9 分以上,测试程序员必看的 7 本书
  8. Ubuntu安装Eclipse-cpp教程(含java运行环境搭建)
  9. 9个月注销超700家艺人经纪公司,小县城如何沦为大明星避税天堂?
  10. uni-app评论组件