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则相对简单点。

各种映射工具性能比较

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值