ModelMapper

ModelMapper 是什么?

是一个 Object To Object 的工具。是利用反射的原理实现的 Object To Object。

有什么用?

在项目中很多时候需要把 entity 和 DTO 两个模型类来回转换,保证 entity 对外是隐私的。而ModelMapper就是为了方便模型转换而实现的一个类库。

为什么选择modelMapper

ModelMapper的目标是让对象映射变得简单,根据惯例自动确定一个对象模型如何映射到另一个对象,就像人类一样,同时为处理特定用例提供一个简单、重构安全的API。

ModelMapper 的优点:
配置简单;
对于 DO-DTO-VO 转换中属性一致的对象转换基本一行代码搞定;
能够快速修正遗漏字段,保证数据一致性,运用场景有:数据清洗、数据同步;
可在转换时增加自定义逻辑;

缺点:
通过反射的方式进行映射,所以性能不如一些主流的映射框架,但是在实际项目中大多数的业务场景使用modelMapper是不会有什么影响的。

工作原理

ModelMapper 由两个单独的过程组成:匹配过程(其中源和目标类型的属性相互匹配)和映射过程(将匹配的属性值从源对象转换为目标对象)
调用该方法时,将分析源类型和目标类型,以确定根据匹配策略和其他配置隐式匹配的属性。然后根据这些匹配进行映射数据。map
即使源对象目标对象及其属性不同,ModelMapper 也会根据配置的匹配策略,尽力确定属性之间的合理匹配。
模型映射器 - 工作原理

官方文档
模型映射器 - 入门

使用

pom引入modelmapper坐标本文以3.0.0为例

<dependency>
  <groupId>org.modelmapper</groupId>
  <artifactId>modelmapper</artifactId>
  <version>3.0.0</version>
</dependency>

映射
源模型

@Data
public class Order {
    private Customer customer;
    private Address billingAddress;
	private String orderCode;
}

@Data
class Customer {
    private Name name;
}

@Data
class Name {
    private String firstName;
    private String lastName;
}

@Data
class Address {
    private String street;
    private String city;
}

目标模型

@Data
public class OrderDTO {
    private String customerFirstName;
    private String customerLastName;
    private String billingStreet;
	private String billingStreetTest;
    private String billingCity;
	private String billingCityTest;
	private String orderCode;
}

简单的 model 转换

public static void main(String[] args) {
    ModelMapper modelMapper = new ModelMapper();
    Order order = new Order();
    Customer customer = new Customer();
    Name name = new Name();
    name.setLastName("张");
    name.setFirstName("三");
    customer.setName(name);
    Address address = new Address();
    address.setCity("上海");
    address.setStreet("安研路");
    order.setCustomer(customer);
    order.setBillingAddress(address);
    OrderDTO orderDTO = modelMapper.map(order, OrderDTO.class);
    String jsonString = JSON.toJSONString(orderDTO);
	// jsonString = {"billingCity":"上海","billingStreet":"安研路","customerFirstName":"三","customerLastName":"张"}
    System.out.println("jsonString = " + jsonString);
}

范型 model 转换

List<orderDTO> orderDTOList = modelMapper.map(orderList, new TypeToken<List<orderDTO>>() {}.getType());

虽然 ModelMapper 会尽最大努力为您隐式匹配源属性和目标属性,但有时可能需要显式定义属性之间的映射。

ModelMapper 支持多种映射方法,允许使用任意组合的方法和字段引用。

public static void main(String[] args) {
    ModelMapper modelMapper = new ModelMapper();
    Order order = new Order();
    Customer customer = new Customer();
    Name name = new Name();
    name.setLastName("张");
    name.setFirstName("三");
    customer.setName(name);
    Address address = new Address();
    address.setCity("上海");
    address.setStreet("安研路");
    order.setCustomer(customer);
    order.setBillingAddress(address);
    OrderDTO orderDTO = modelMapper
            .typeMap(Order.class, OrderDTO.class)
            .addMappings(mapper -> {
                mapper.map(src -> src.getBillingAddress().getStreet(),
                        OrderDTO::setBillingStreetTest);
                mapper.map(src -> src.getBillingAddress().getCity(),
                        OrderDTO::setBillingCityTest);
            })
            .map(order);
    String jsonString = JSON.toJSONString(orderDTO);
    // jsonString = {"billingCity":"上海","billingCityTest":"上海","billingStreet":"安研路","billingStreetTest":"安研路","customerFirstName":"三","customerLastName":"张"}
    System.out.println("jsonString = " + jsonString);
}

深度映射

此示例将目标类型的方法映射到源类型的方法层次结构,允许在源方法和目标方法之间进行深度映射:setAgegetCustomer().getAge()

typeMap.addMappings(mapper -> mapper.map(src -> src.getCustomer().getAge(), PersonDTO::setAge));

此示例将目标类型的方法层次结构映射到源类型的属性层次结构:getCustomer().setName()person.getFirstName()

typeMap.addMappings(mapper -> mapper.<String>map(src -> src.getPerson().getFirstName(), (dest, v) -> dest.getCustomer().setName(v)));

注意:为了填充目标对象,深度映射要求方法具有相应的突变器,例如 一种方法 或 可访问字段 getCustomer setCustomer customer

跳过属性

虽然 ModelMapper 隐式创建从源类型到目标类型中每个属性的映射,但有时可能需要跳过某些目标属性的映射。

此示例指定在映射过程中应跳过目标类型的方法:setName

typeMap.addMappings(mapper -> mapper.skip(Destination::setName));

变换器

转换器允许在将源映射到目标属性时进行自定义转换(有关详细信息,请参阅转换器的常规页面)。

考虑这个转换器,它扩展了AbstractConverter并将字符串转换为大写字符串:

Converter<String, String> toUppercase =
    ctx -> ctx.getSource() == null ? null : ctx.getSource().toUppercase();
// 使用转换器从源属性映射到目标属性很简单:toUppercase
typeMap.addMappings(mapper -> mapper
	.using(toUppercase)
	.map(Person::getName, PersonDTO::setName));

或者我们也可以使用 lambda 表达式 using

typeMap.addMappings(mapper -> mapper
	.using(ctx -> ((String) ctx.getSource()).toUpperCase())
	.map(Person::getName, PersonDTO::setName));

使用具有深度映射的转换器时,映射引用的最后一个源和目标属性将传递给变换器:

typeMap.addMappings(
    // toUppercase 将使用getFirstName()和setName()中的属性类型调用
    mapper -> mapper
    	.using(toUppercase)
    	.<String>map(
    		src -> src.getPerson().getFirstName(), 
    		(dest, v) -> dest.getCustomer().setName(v)
    		)
    	);

提供者

提供程序允许,允许在映射之前提供自己的目标属性和类型实例(有关详细信息,请参阅提供程序上的常规页)。

请考虑此提供程序,它提供实例:Person

Provider<Person> personProvider = req -> new Person();

配置用于特定属性映射很简单:personProvider

typeMap.addMappings(mapper -> mapper.with(personProvider).map(Source::getPerson, Destination::setPerson));

或者我们也可以使用 lambda 表达式。with

typeMap.addMappings(mapper ->
	mapper.with(req -> new Person()).map(Source::getPerson, Destination::setPerson));
public static void main(String[] args) {
    ModelMapper modelMapper = new ModelMapper();
    Order order = new Order();
    Customer customer = new Customer();
    Name name = new Name();
    name.setLastName("张");
    name.setFirstName("三");
    customer.setName(name);
    Address address = new Address();
    address.setCity("上海");
    address.setStreet("安研路");
    order.setCustomer(customer);
    order.setBillingAddress(address);
    order.setOrderCode("12394234323");
    OrderDTO orderDTO = modelMapper
            .typeMap(Order.class, OrderDTO.class)
            .addMappings(mapper -> {
                mapper
                        .with(r -> "自定义提供者,属性覆盖")
                        .map(Order::getOrderCode, OrderDTO::setOrderCode);
            })
            .map(order);
    String jsonStringOrder = JSON.toJSONString(order);
    String jsonStringOrderDto = JSON.toJSONString(orderDTO);
    System.out.println("jsonStringOrderDto = " + jsonStringOrderDto);
    System.out.println("jsonStringOrder = " + jsonStringOrder);
    // jsonStringOrderDto = {"billingCity":"上海","billingStreet":"安研路","customerFirstName":"三","customerLastName":"张","orderCode":"自定义提供者,属性覆盖"}
    // jsonStringOrder = {"billingAddress":{"city":"上海","street":"安研路"},"customer":{"name":{"firstName":"三","lastName":"张"}},"orderCode":"12394234323"}
}

条件映射

可以通过提供映射来使目标属性的映射成为条件。Condition

请考虑此条件,如果源类型不是:null

Condition notNull = ctx -> ctx.getSource() != null;
// 我们可以使用 Condition 指定仅在源不为 null 时对属性进行映射:notNull
typeMap.addMappings(mapper -> mapper
	.when(notNull)
	.map(Person::getName, PersonDTO::setName));

在此示例中,如果源不为 null,则将跳过映射,否则映射将从源对象的方法继续:setName getName

typeMap.addMappings(mapper -> mapper.when(notNull).skip(PersonDTO::setName));

问题排查 验证

虽然 ModelMapper 会尝试将每个目标属性与源属性进行匹配,但偶尔也会有一些目标属性找不到匹配项。若要验证所有目标属性是否匹配,请调用该方法:validate

modelMapper.validate();

如果验证失败,将抛出一条有用的消息,描述任何不匹配的目标属性。ValidationException

处理验证错误

  • 要解决由不匹配的目标导致的验证错误,您有两个选择:
    • 创建映射或忽略任何不匹配属性的映射
    • 调整配置以尝试匹配任何不匹配的属性

或者,可以接受不执行任何操作,在这种情况下,在映射过程中将跳过任何不匹配的目标属性。

 public static void main(String[] args) {
        ModelMapper modelMapper = new ModelMapper();
        Order order = new Order();
        Customer customer = new Customer();
        Name name = new Name();
        name.setLastName("张");
        name.setFirstName("三");
        customer.setName(name);
        Address address = new Address();
        address.setCity("上海");
        address.setStreet("安研路");
        order.setCustomer(customer);
        order.setBillingAddress(address);
        order.setOrderCode("12394234323");
        OrderDTO orderDTO = modelMapper.map(order,OrderDTO.class);
        modelMapper.validate();
    }
Exception in thread "main" org.modelmapper.ValidationException: ModelMapper validation errors:

1) Unmapped destination properties found in TypeMap[Order -> OrderDTO]:

	com.entity.OrderDTO.setBillingStreetTest()
	com.entity.OrderDTO.setBillingCityTest()

1 error
	at org.modelmapper.internal.Errors.throwValidationExceptionIfErrorsExist(Errors.java:246)
	at org.modelmapper.ModelMapper.validate(ModelMapper.java:547)
	at com.Test.main(Test.java:29)

通过异常可以看到 setBillingStreetTest() 和 setBillingCityTest() 未找到映射的目标属性,所以该值映射不到。

通过 处理不匹配 方式既可解决

配置

模型映射器 - 配置

springBoot 中可以通过 ico 统一管理使用

@Configuration
public class ModelMapperConfig {
    /**
     * 范型 转换 modelMapper.map(raceModels, new TypeToken<List<object>>() {}.getType());
     */

	@Bean
	public ModelMapper modelMapper() {
	    ModelMapper modelMapper = new ModelMapper();
	    modelMapper.getConfiguration()
	            // 开启字段匹配
	            .setFieldMatchingEnabled(true)
	            // 设置最大方法级别 为 private
	            .setMethodAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
	            // 设置匹配策略模式为 严格
	            .setMatchingStrategy(MatchingStrategies.STRICT)
	            // 设置 如果源对象如果为空 不拷贝到 目标对象  源user name = null  目标userdto name = xixi     拷贝的时候  userdot name 不会被覆盖成 null
	            .setSkipNullEnabled(Boolean.TRUE);
	    return modelMapper;
	}
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值