业务开发必看-实体映射工具推荐

前言

声明:java

一、DO(业务实体对象),DTO(数据传输对象)。spring

在一个成熟的工程中,尤为是如今的分布式系统中,应用与应用之间,还有单独的应用细分模块以后,DO 通常不会让外部依赖,这时候须要在提供对外接口的模块里放 DTO 用于对象传输,也便是 DO 对象对内,DTO对象对外,DTO 能够根据业务须要变动,并不须要映射 DO 的所有属性。shell

  • 在咱们对外暴露的Dubbo接口,通常这样定义接口类
/** * 获取营销信息 * * @param hallId 场馆编号 * @param modelCode 车型代码 * @return */
    BrandMarketInfoDTO getBrandMarketInfo(String hallId, String modelCode);
复制代码
  • 在和数据持久层映射实现的接口中,通常咱们会这么写
/** * 获取水牌营销信息 * * @param hallId * @param modelCode * @return */
HallCarManageDO getBoardMarketInfo(@Param("hallId") String hallId, @Param("modelCode") String modelCode);
复制代码

可是咱们HallCarManageDO和BrandMarketInfoDTO中的属性和属性类型不是相等的。这种 对象与对象之间的互相转换,就须要有一个专门用来解决转换问题的工具,毕竟每个字段都 get/set 会很麻烦。缓存

常见工具类及实现

如下列举被广大开发工程师经常使用的Bean属性复制工具安全

  • Spring.BeanUtils
  • Cglib.BeanCopier
  • MapStruct

如下选取属性赋值的功能来对比每一个工具类的不一样app

BeanUtils

import org.springframework.beans.BeanUtils;

/** * @author james mu * @date 2019/10/22 */
public class PojoUtils {

    /** * * @param source 源对象 * @param clazz 目标对象 * * @return 复制属性后的对象 */
    public static <T> T copyProperties(Object source, Class<T> clazz) {
        if (source == null) {
            return null;
        }
        T target;
        try {
            target = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new RuntimeException("经过反射建立对象失败");
        }
        BeanUtils.copyProperties(source, target);
        return target;
    }

}

复制代码

springframework的BeanUtils也是经过java内省机制获取getter/setter,而后经过反射调用从而实现属性复制。分布式

BeanCopier

依赖引用

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.5</version>
</dependency>
复制代码

工具类实现

import net.sf.cglib.beans.BeanCopier;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/** * @author james mu * @date 2019/10/22 */
public class BeanCopierUtils {

    /** * BeanCopier拷贝速度快,性能瓶颈出如今建立BeanCopier实例的过程当中。 * 因此,把建立过的BeanCopier实例放到缓存中,下次能够直接获取,提高性能 */
    public static Map<String, BeanCopier> beanCopierMap = new ConcurrentHashMap<String, BeanCopier>();

    /** * cp 对象赋值 * * @param source 源对象 * @param target 目标对象 */
    public static void copyProperties(Object source, Object target) {
        String beanKey = generateKey(source.getClass(), target.getClass());
        BeanCopier copier = null;
        if (!beanCopierMap.containsKey(beanKey)) {
            copier = BeanCopier.create(source.getClass(), target.getClass(), false);
            beanCopierMap.put(beanKey, copier);
        } else {
            copier = beanCopierMap.get(beanKey);
        }
        copier.copy(source, target, null);
    }

    private static String generateKey(Class<?> class1, Class<?> class2) {
        return class1.toString() + class2.toString();
    }
}

复制代码
  1. 使用动态代理,生成字节码类,再经过Java反射成Class,调用其copy方法。ide

  2. 你们能够看到这里用到了ConcurrentHashMap存取copier,由于BeanCopier.create使用了缓存,该过程也消耗资源,建议全局只初始化一次。工具

    自定义转换器

  3. 支持自定义转换器。post

MapStruct

MapSturct 是一个生成类型安全, 高性能且无依赖的 JavaBean 映射代码的注解处理器(annotation processor)。

抓一下重点:

  1. 注解处理器
  2. 能够生成 JavaBean 之间那的映射代码
  3. 类型安全, 高性能, 无依赖性

从字面的理解, 咱们能够知道, 该工具能够帮咱们实现 JavaBean 之间的转换, 经过注解的方式。

同时, 做为一个工具类,相比于手写, 其应该具备便捷, 不容易出错的特色。

入门

依赖引用

<dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>1.3.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>1.3.0.Final</version>
            <scope>provided</scope>
        </dependency>
复制代码

案例演示

UserDO

import lombok.Data;
import lombok.ToString;

import java.util.Date;

/** * @author james mu * @date 2019/10/22 */
@Data
@ToString
public class UserDO {
    private String name;
    private String password;
    private Integer age;
    private Date birthday;
    private String sex;
}


复制代码

UserDTO

import lombok.Data;
import lombok.ToString;


/** * @author james mu * @date 2019/10/22 */
@Data
@ToString
public class UserDTO {
    private String name;
    private String age;
    private String birthday;
    private String gender;
}
复制代码

UserConvertUtils

import com.sanshengshui.javabeanconvert.DO.UserDO;
import com.sanshengshui.javabeanconvert.DTO.UserDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;

/** * @author james mu * @date 2019/10/22 */
@Mapper
public interface UserConvertUtils {

    UserConvertUtils INSTANCE = Mappers.getMapper(UserConvertUtils.class);

    /** * 类型转换 * * @param userDO UserDO数据持久层类 * @return 数据传输类 */
    @Mappings({
               @Mapping(target = "gender", source = "sex")
            })
    UserDTO doToDTO(UserDO userDO);
}


复制代码
  1. DTO与DO中属性名相同时候默认映射,(好比name),属性名相同属性类型不一样也会映射,(好比birthday,一个Data,一个String)
  2. DTO与DO中属性名不一样的,须要经过@Mapping明确关系来造成映射(如sex对应gender)
  3. 无映射关系属性被忽略(如UserEntity的password)

结果以下:

UserDO(name=snow, password=123, age=20, birthday=Tue Oct 22 17:10:19 CST 2019, sex=男)
+-+-+-+-+-+-+-+-+-+-+-
UserDTO(name=snow, age=20, birthday=19-10-22 下午5:10, gender=男)

复制代码

MapStruct分析

上面中, 我写了3个步骤来实现了从 UserDTOUserDO 的转换。

那么, 做为一个注解处理器, 经过MapStruct 生成的代码具备怎么样的优点呢?

高性能

Java反射原理和反射低的缘由:juejin.im/post/5da33b…

这是相对反射来讲的, 反射须要去读取字节码的内容, 花销会比较大。 而经过 MapStruct 来生成的代码, 其相似于人手写。 速度上能够获得保证。

前面例子中生成的代码能够在编译后看到。 在 target/generated-sources/annotations 里能够看到。

对应的代码

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2019-10-22T17:10:17+0800",
    comments = "version: 1.3.0.Final, compiler: javac, environment: Java 1.8.0_222 (Azul Systems, Inc.)"
)
public class UserConvertUtilsImpl implements UserConvertUtils {

    @Override
    public UserDTO doToDTO(UserDO userDO) {
        if ( userDO == null ) {
            return null;
        }

        UserDTO userDTO = new UserDTO();

        userDTO.setGender( userDO.getSex() );
        userDTO.setName( userDO.getName() );
        if ( userDO.getAge() != null ) {
            userDTO.setAge( String.valueOf( userDO.getAge() ) );
        }
        if ( userDO.getBirthday() != null ) {
            userDTO.setBirthday( new SimpleDateFormat().format( userDO.getBirthday() ) );
        }

        return userDTO;
    }
}

复制代码

能够看到其生成了一个实现类, 而代码也相似于咱们手写, 通俗易懂。

性能比较

测试在两个简单的Bean之间转换的耗时,执行次数分别为十、100、1k、10k、100k,时间单位为ms。

总结

虽然反射效率低,但这个时间是很小很小的。根据不一样工具的性能及功能维度,我的建议当对象转换操做较少或者应用对性能要求较高时,尽可能不采用工具,而是手写getter/setter;在不考虑性能的状况下,普通的对象转换可使用Cglib.BeanCopier,复杂的对象转换使用MapStruct。

相关文章
相关标签/搜索