JavaBeans映射工具比较
定义一些基础对象
// 定义转换的实体
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 {
@Override
public 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> {
@Override
public Enum convertTo(Integer source, Type<Enum> destinationType, MappingContext mappingContext) {
try {
if (Enum.class.isAssignableFrom(destinationType.getRawType())) {
// Integer转Enum
for (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;
}
@Override
public 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;
}
@Override
public 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());
}
@Override
public 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;
}
@Override
public 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());
}
@Override
public 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> {
@Override
public void mapAtoB(OrikaDTO orikaDTO, OrikaPO orikaPO, MappingContext context) {
orikaPO.setDownloadResourceTypeEnum(orikaDTO.getDownloadResourceTypeEnum().getCode());
}
@Override
public void mapBtoA(OrikaPO orikaPO, OrikaDTO orikaDTO, MappingContext context) {
orikaDTO.setDownloadResourceTypeEnum(TypeEnum.getTypeByCode(orikaPO.getDownloadResourceTypeEnum()));
}
}
public class OrikaPOFactory implements ObjectFactory<OrikaPO> {
@Override
public 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> {
@Override
public String convertTo(LocalDateTime source, Type<String> destinationType, MappingContext mappingContext) {
return MyDateUtils.formatAppTime(source);
}
@Override
public LocalDateTime convertFrom(String source, Type<LocalDateTime> destinationType, MappingContext mappingContext) {
return null;
}
}
public class GradeConverter extends BidirectionalConverter<GradeEnum, String> {
@Override
public String convertTo(GradeEnum source, Type<String> destinationType, MappingContext mappingContext) {
return source.getDesc();
}
@Override
public 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> {
@Override
public String convertTo(IdentityTypeEnum source, Type<String> destinationType, MappingContext mappingContext) {
return source.getDesc();
}
@Override
public 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> {
@Override
public String convertTo(CityEnum source, Type<String> destinationType, MappingContext mappingContext) {
return source.getDesc();
}
@Override
public 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<>();
@Override
public 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 {
@Resource
private IShareService shareService;
@Resource
private IIdentityService identityService;
@Resource
private ILinkService linkService;
@Override
public 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则相对简单点。