Declaration: 1, DO (business entity object), DTO (data transfer object). 2. I use Lombok in my code, so if you don't know about Lombok, just ignore it.Copy the code

In a mature project, especially in today’s distributed system, when there are separate application subdivision modules between applications, DO generally does not allow external dependence. At this time, DTO should be placed in modules providing external interfaces for object transmission, that is, DO objects for internal and DTO objects for external. Dtos can be changed according to business needs without mapping all attributes of DO.

This kind of object to object conversion, you need to have a special tool to solve the conversion problem, after all, every field get/set is cumbersome.

MapStruct is such an attribute mapping tool, just need to define a Mapper interface, MapStruct will automatically implement the mapping interface, avoid complex mapping implementation. MapStruct official website address: mapstruct.org/

Maven dependencies are introduced in the project

<properties> <mapstruct.version> 1.2.0.final </mapstruct.version> </properties> <dependencies> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-jdk8</artifactId> <version>${mapstruct.version}</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </dependency> </dependencies>Copy the code

The basic mapping

Here we define two DO objects Person and User, where User is an attribute of Person, and a DTO object PersonDTO

@NoArgsConstructor @AllArgsConstructor @Data public class Person { private Long id; private String name; private String email; private Date birthday; private User user; } @NoArgsConstructor @AllArgsConstructor @Data public class User { private Integer age; } @NoArgsConstructor @AllArgsConstructor @Data public class PersonDTO { private Long id; private String name; /** * corresponds to Person.user.age */ private Integer age; private String email; /** * private Date birth; /** * Extend birthDay to dateFormat */ private String birthDateFormat; /** * private String birthExpressionFormat; /** private String birthExpressionFormat; }Copy the code

Write a Mapper interface PersonConverter with two methods, one is a single entity mapping, the other is a List mapping

If the name of the source object attribute is the same as that of the target object attribute, the corresponding attribute will be automatically mapped. If the name of the source object attribute is the same as that of the target object attribute, the corresponding attribute will be automatically mapped. If the name of the source object attribute is the same as that of the target object, the corresponding attribute will be automatically mapped. BirthDateFormat adds dateFormat to the conversion format or birthExpressionFormat adds expression. If you do not want to map an attribute, You can add ignore=true

@Mapper
public interface PersonConverter {
    PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
    @Mappings({
        @Mapping(source = "birthday", target = "birth"),
        @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
        @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
        @Mapping(source = "user.age", target = "age"),
        @Mapping(target = "email", ignore = true)
    })
    PersonDTO domain2dto(Person person);

    List<PersonDTO> domain2dto(List<Person> people);
}
Copy the code

After compiling MapStruct, the IDE will compile it for us manually or when we start it, automatically generating the corresponding implementation classes under target/classes

Manually compile the command MVN compileCopy the code

Attention!! The following PersonConverterImpl is automatically generated, not self-written!

public class PersonConverterImpl implements PersonConverter { public PersonConverterImpl() { } public PersonDTO domain2dto(Person person) { if (person == null) { return null; } else { PersonDTO personDTO = new PersonDTO(); personDTO.setBirth(person.getBirthday()); if (person.getBirthday() ! = null) { personDTO.setBirthDateFormat((new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(person.getBirthday())); } Integer age = this.personUserAge(person); if (age ! = null) { personDTO.setAge(age); } personDTO.setId(person.getId()); personDTO.setName(person.getName()); personDTO.setBirthExpressionFormat(DateFormatUtils.format(person.getBirthday(), "yyyy-MM-dd HH:mm:ss")); return personDTO; } } public List<PersonDTO> domain2dto(List<Person> people) { if (people == null) { return null; } else { List<PersonDTO> list = new ArrayList(people.size()); Iterator var3 = people.iterator(); while(var3.hasNext()) { Person person = (Person)var3.next(); list.add(this.domain2dto(person)); } return list; } } private Integer personUserAge(Person person) { if (person == null) { return null; } else { User user = person.getUser(); if (user == null) { return null; } else { Integer age = user.getAge(); return age == null ? null : age; }}}}Copy the code

Write a unit test class PersonConverterTest to test it and see how it works

public class PersonConverterTest { @Test public void test() { Person person = new Person(1L,"zhige","[email protected]",new Date(),new User(1)); PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person); assertNotNull(personDTO); assertEquals(personDTO.getId(), person.getId()); assertEquals(personDTO.getName(), person.getName()); assertEquals(personDTO.getBirth(), person.getBirthday()); String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss"); assertEquals(personDTO.getBirthDateFormat(),format); assertEquals(personDTO.getBirthExpressionFormat(),format); List<Person> people = new ArrayList<>(); people.add(person); List<PersonDTO> personDTOs = PersonConverter.INSTANCE.domain2dto(people); assertNotNull(personDTOs); }}Copy the code

For one more

MapStruct can map objects of several types to another type, such as converting multiple DO objects to Dtos

example

  • Two DO objects, Item and Sku, and one DTO object, SkuDTO
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Item {
    private Long id;
    private String title;
}

@NoArgsConstructor
@AllArgsConstructor
@Data
public class Sku {
    private Long id;
    private String code;
    private Integer price;
}

@NoArgsConstructor
@AllArgsConstructor
@Data
public class SkuDTO {
    private Long skuId;
    private String skuCode;
    private Integer skuPrice;
    private Long itemId;
    private String itemName;
}
Copy the code
  • Create an ItemConverter interface, and MapStruct automatically implements that interface
@Mapper
public interface ItemConverter {
    ItemConverter INSTANCE = Mappers.getMapper(ItemConverter.class);

    @Mappings({
            @Mapping(source = "sku.id",target = "skuId"),
            @Mapping(source = "sku.code",target = "skuCode"),
            @Mapping(source = "sku.price",target = "skuPrice"),
            @Mapping(source = "item.id",target = "itemId"),
            @Mapping(source = "item.title",target = "itemName")
    })
    SkuDTO domain2dto(Item item, Sku sku);
}
Copy the code
  • Create a test class that maps two DO objects, Item and Sku, to a DTO object, SkuDTO
public class ItemConverterTest { @Test public void test() { Item item = new Item(1L, "iPhone X"); Sku sku = new Sku(2L, "phone12345", 1000000); SkuDTO skuDTO = ItemConverter.INSTANCE.domain2dto(item, sku); assertNotNull(skuDTO); assertEquals(skuDTO.getSkuId(),sku.getId()); assertEquals(skuDTO.getSkuCode(),sku.getCode()); assertEquals(skuDTO.getSkuPrice(),sku.getPrice()); assertEquals(skuDTO.getItemId(),item.getId()); assertEquals(skuDTO.getItemName(),item.getTitle()); }}Copy the code

Custom methods can be added

Default PersonDTO personToPersonDTO(Person Person) {//hand-written mapping logic} // such as PersonConverter Add the following default Boolean convert2Bool (Integer value) {if (value = = null | | value < 1) {return Boolean. FALSE; } else { return Boolean.TRUE; } } default Integer convert2Int(Boolean value) { if (value == null) { return null; } if (Boolean.TRUE.equals(value)) { return 1; } return 0; } / / test class PersonConverterTest join assertTrue (PersonConverter. INSTANCE. Convert2Bool (1)); assertEquals((int)PersonConverter.INSTANCE.convert2Int(true),1);Copy the code

Update the target object if it already has one

// Add the following to PersonConverter, for example: @inheritConfiguration @inheritConfiguration (name = "domain2dto") void update(Person Person, @MappingTarget PersonDTO personDTO); // Add the following Person Person = new Person(1L,"zhige","[email protected]",new Date(),new User(1)) to the test class PersonConverterTest; PersonDTO personDTO = PersonConverter.INSTANCE.domain2dto(person); assertEquals("zhige", personDTO.getName()); person.setName("xiaozhi"); PersonConverter.INSTANCE.update(person, personDTO); assertEquals("xiaozhi", personDTO.getName());Copy the code

Spring injection

PersonConverter INSTANCE = Mappers. GetMapper (personConverter.class);Copy the code

Spring =” componentModel “; Spring =” componentModel”

@Mapper(componentModel="spring")
public interface PersonConverter {
    @Mappings({
        @Mapping(source = "birthday", target = "birth"),
        @Mapping(source = "birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
        @Mapping(target = "birthExpressionFormat", expression = "java(org.apache.commons.lang3.time.DateFormatUtils.format(person.getBirthday(),\"yyyy-MM-dd HH:mm:ss\"))"),
        @Mapping(source = "user.age", target = "age"),
        @Mapping(target = "email", ignore = true)
    })
    PersonDTO domain2dto(Person person);
}
Copy the code

This time test class change, I use the form of Spring Boot

@RunWith(SpringRunner.class) @SpringBootTest(classes = BaseTestConfiguration.class) public class PersonConverterTest { @autoWired private PersonConverter PersonConverter; @Test public void test() { Person person = new Person(1L,"zhige","[email protected]",new Date(),new User(1)); PersonDTO personDTO = personConverter.domain2dto(person); assertNotNull(personDTO); assertEquals(personDTO.getId(), person.getId()); assertEquals(personDTO.getName(), person.getName()); assertEquals(personDTO.getBirth(), person.getBirthday()); String format = DateFormatUtils.format(personDTO.getBirth(), "yyyy-MM-dd HH:mm:ss"); assertEquals(personDTO.getBirthDateFormat(),format); assertEquals(personDTO.getBirthExpressionFormat(),format); }}Copy the code

I added a configuration class to the test path

@EnableAutoConfiguration
@Configuration
@ComponentScan
public class BaseTestConfiguration {
}
Copy the code

MapStruct annotation keywords

The @mapper attribute specifies the type of the implementation class. Two default values are used: By default, we can get the instance object by Mappers. GetMapper (Class) : Spring: The @Component annotation is automatically added to the implementation Class of the interface, and @Mapping can be injected by @autowired: Attribute mapping. If the source object attribute is the same as the target object name, the system automatically maps the corresponding attribute source: source attribute Target: target attribute dateFormat: String to Date, using SimpleDateFormat. This value is the Date format of SimpleDateFormat. Ignore: Ignore @mappings: Configure multiple @mappingTargets to update existing objects @inheritConfiguration to inherit the configurationCopy the code

This article just wrote some commonly used some simple function, can more detailed to read the official document: mapstruct.org/documentati…

If you think the content is good, you can pay attention to my wechat official number: Zhige-Me. I look forward to meeting you and growing up together!