MapStruct_Concepts, how to use, subsets and mapping, merging, Spring methods, expressions, custom aspect processing

Article directory

  • ①. What is MapStruct?
  • ②. How to use MapStruct?
  • ③. Subsets and mapping
  • ④. Merge mapping
  • ⑤. Spring dependency injection
  • ⑥. Constants, default values and expressions
  • ⑦. Customized aspect processing

①. What is MapStruct?

  • ①. MapStruct is an object attribute mapping tool based on Java annotations. When using it, we only need to define the object attribute mapping rules in the interface, and it can automatically generate mapping implementation classes without using reflection. It has excellent performance and can implement various complex mapping

  • ②. In daily CRUD work, it is often necessary to convert between PO, VO, and DTO. For simple object conversion, using BeanUtils is basically enough, but for complex conversion, if you use it, you have to write a bunch of Getter and Setter methods. BeanUtils is a big deal. It can only map the same attributes, or when the attributes are the same, it allows fewer object attributes to be mapped; but when the mapped attribute data type is modified or the mapped field name is modified, then Will cause mapping to fail

  • ③. IDEA downloads the mapstruct support plug-in

②. How to use MapStruct?

  • ①. Introduce MapStruct dependency
 <properties>
        <lombok.version>1.18.12</lombok.version>
        <mapstruct.version>1.4.2.Final</mapstruct.version>
    </properties>
<!--MapStruct related dependencies-->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
<scope>compile</scope>
</dependency>
\t\t
    <!--You need to add the following plug-in, otherwise an error ClassNotFoundException will be reported-->
    <build>
        <plugins>
            <!-- MapStruct compiler plug-in -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>org.mapstruct</groupId>
                            <artifactId>mapstruct-processor</artifactId>
                            <version>1.4.2.Final</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
  • ②. Create the case entity class we need
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class User {<!-- -->
    private Integer id;//user id
    private String userName;//username
    private String password; //Password
    private Date birthday;//birthday
    private String tel;//phone number
    private String email; //Email
    private String idCardNo;//ID card number
    private String icon; //Avatar
    private Integer gender;//gender
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class UserVo {<!-- -->
    private Long id;//user id
    private String userName;//username
    private String password; //Password
// A different type than the User object
    private String birthday;//birthday
    //A name different from User
    private String telNumber;//phone number
    private String email; //Email
    private String idCardNo;//ID card number
    private String icon; //Avatar
    private Integer gender;//gender
}
  • ③. Create a mapping interface (purpose: to realize the mapping of attributes with the same name and type, attributes with different names, and attributes of different types)
/**
 * unmappedTargetPolicy:
 * The processing strategy when the target attribute does not exist. The optional values are: IGNORE default value, WARN and ERROR.
 * IGNORE default value: ignore unmapped source properties
 * WARN: Any unmapped source properties will cause a warning at build time, based on the warning of javax.tools.Diagnostic.Kind.WARNING.
 * ERROR: Any unmapped source properties will cause mapping code generation to fail.
 *
 */
@Mapper(unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface UserMapper {<!-- -->
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "tel",target = "telNumber")
    @Mapping(source = "birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
    UserVo convertToVo(User user);
}
  • ④. Case demonstration
@RestController
@RequestMapping("/testController")
@Slf4j
public class TestController {<!-- -->

    @GetMapping("/mapStructToVo")
    public String mapStructToVo() {<!-- -->
        User user = new User();
        user.setId(1).setEmail("[email protected]").setUserName("tang").setBirthday(new Date()).setTel("18774149799");
        UserVo userVo = UserMapper.INSTANCE.convertToVo(user);
        // {"birthday":"2023-10-07","email":"[email protected]","id":1,"telNumber":\ "18774149799","userName":"tang"}
        System.out.println(JSON.toJSONString(userVo));
        return JSON.toJSONString(userVo);
    }
}

③. Subsets and mappings

  • ①. MapStruct also supports situations where objects containing sub-objects also need to be converted

  • ②. There is an order PO object Order, nested with User and Product objects

@Data
@EqualsAndHashCode(callSuper = false)
public class Order {<!-- -->
    private Long id;
    private String orderNo;//order number
    private Date createTime;
    private String receiverAddress; //Receiving address
    private User user;//The user to whom the order belongs
    private List<Product> productList; //Product collection
}
@Data
@Accessors(chain = true)
@EqualsAndHashCode(callSuper = false)
public class Product {<!-- -->
    private Long id;
    private String productSn;
    private String name;
    private String subTitle;
    private String brandName;
    private BigDecimal price;
    private Integer count;//number of items
    private Date createTime;
}
  • ③. We need to convert it to an OrderDo object. The two sub-objects UserVo and ProductVo included in OrderDo also need to be converted;
@Data
@EqualsAndHashCode(callSuper = false)
public class OrderVo {<!-- -->
    private Long id;
    private String orderNo; //Order number
    private Date createTime;
    private String receiverAddress; //Receiving address
    //Sub-object mapping Dto
    private UserVo userVo;//The user to whom the order belongs
    //Sub-object array mapping Dto
    private List<ProductVo> productVoList; //Product collection
}
@Data
@EqualsAndHashCode(callSuper = false)
public class ProductVo {<!-- -->
    //Use constants
    private Long id;
    //Use expression to generate attributes
    private String productSn;
    private String name;
    private String subTitle;
    private String brandName;
    private BigDecimal price;
    //use default value
    private Integer number;//number of items
    private Date createTime;
}
  • ④. Use uses to inject the conversion Mapper of the sub-object, and then set the attribute mapping rules through @Mapping.
@Mapper(uses = {<!-- -->UserMapper.class,ProductMapper.class})
public interface OrderMapper {<!-- -->
    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);

    @Mapping(source = "user",target = "UserVo")
    @Mapping(source = "productList",target = "productVoList")
    OrderVo convertToVo(Order order);
}
@Mapper(imports = {<!-- -->UUID.class})
public interface ProductMapper {<!-- -->
    ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);

    @Mapping(target = "id",constant = "-1L")
    @Mapping(source = "count",target = "number",defaultValue = "1")
    @Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
    ProductVo convertToVo(Product product);
}
  • ⑤. Call the conversion method toDto directly through the INSTANCE instance in Mapper;
 @GetMapping("/mapStructToSubVo")
    public String mapStructToSubVo() {<!-- -->
        //Create a user object
        User user = new User();
        user.setId(1).setEmail("[email protected]").setUserName("tang")
                .setBirthday(new Date()).setTel("18774149799");
        //Create productList
        List<Product> productList = new ArrayList<>();
        productList.add(new Product().setCount(3).setName("test-nameA"));
        productList.add(new Product().setCount(7).setName("test-nameB"));
        Order order = new Order();
        order.setUser(user).setProductList(productList);
        OrderVo orderVo = OrderMapper.INSTANCE.convertToVo(order);
        // {"productVoList":[{"id":-1,"name":"test-nameA","number":3,"productSn":" d7cacdd0-4a13-46b1-a76b-fba7607d68ea"},{"id":-1,"name":"test-nameB","number":7,"productSn" :"18f7c91e-c5f1-4bb6-8ae3-6e1e5847f03c"}],"userVo":{"birthday":"2023-10-12","email":"845195485@ qq.com","id":1,"telNumber":"18774149799","userName":"tang"}}
        System.out.println(JSON.toJSONString(orderVo));
        return JSON.toJSONString(orderVo);
    }

④. Merge mapping

  • ①. MapStruct supports mapping multiple object attributes to one object

  • ②. Map some attributes of User and Order to UserOrderDto

@Data
public class UserOrderVo {<!-- -->
    private Long id;//user id
    private String userName;//username
    private String password; //Password
    //Attributes different from PO type
    private String birthday;//birthday
    //Attributes different from PO name
    private String telNumber;//phone number
    private String email;
    private String idCardNo;//ID card number
    private String icon; //Avatar
    private Integer gender;//gender
    private String orderNo; //Order number
    private String receiverAddress; //User's delivery address
}
  • ③. Add the toUserOrderVo method in Mapper. It should be noted here that since there are two attributes in the parameter, the source needs to be specified through the parameter name. The name of the attribute to prevent conflicts. Both parameters have the id attribute.
@Mapper
public interface UserMapper {<!-- -->
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);

    @Mapping(source = "user.tel",target = "telNumber")
    @Mapping(source = "user.birthday",target = "birthday",dateFormat = "yyyy-MM-dd")
    @Mapping(source = "user.id",target = "id")
    @Mapping(source = "order.orderNo", target = "orderNo")
    @Mapping(source = "order.receiverAddress", target = "receiverAddress")
    UserOrderVo toUserOrderVo(User user, Order order);
}
  • ④. Test
 @ApiOperation(value = "Combined Mapping")
    @GetMapping("/compositeMapping")
    public String compositeMapping() {<!-- -->
        //Create a new user object
        User user = new User();
        user.setBirthday(new Date()).setTel("110");
        //Create a new Order object
        Order order = new Order();
        order.setReceiverAddress("Hunan Changsha Test").setOrderNo("123456789");
        // {"birthday":"2023-10-12","orderNo":"123456789","receiverAddress":"Hunan Changsha Test","telNumber" :"110"}
        UserOrderVo userOrderVo = UserMapper.INSTANCE.toUserOrderVo(user,order);
        System.out.println(JSON.toJSONString(userOrderVo));
        return JSON.toJSONString(userOrderVo);
    }

⑤. Spring dependency injection

  • ①. If we want to use dependency injection, we only need to set the componentModel parameter of the @Mapper annotation to spring. In this way, when generating the interface implementation class, MapperStruct will add the @Component annotation to it.
@Mapper(componentModel = "spring")
public interface UserSpringMapper {<!-- -->


    @Mappings({<!-- -->
            @Mapping(source = "tel", target = "telNumber"),
            @Mapping(source = "birthday", target = "birthday", dateFormat = "yyyy-MM-dd")
    })
    UserVo convertToVo(User user);
}
  • ②. Test data
 @Autowired
    private UserSpringMapper userMapper;
    @GetMapping("/mapStructToVoSpring")
    public String mapStructToVoSpring() {<!-- -->
        User user = new User();
        // {"birthday":"2023-10-12","email":"[email protected]","id":1,"telNumber":\ "18774149733","userName":"tang"}
        user.setId(1).setEmail("[email protected]").setUserName("tang").setBirthday(new Date()).setTel("18774149733");
        UserVo userVo = userMapper.convertToVo(user);
        System.out.println(JSON.toJSONString(userVo));
        return JSON.toJSONString(userVo);
    }

⑥. Constants, default values and expressions

  • ①. When using MapStruct to map attributes, we can set the attributes to constants or default values, or we can write expressions in Java to automatically generate attributes
@Data
@Accessors(chain = true)
public class Product {<!-- -->
    private Long id;
    private String productSn;
    private String name;
    private String subTitle;
    private String brandName;
    private BigDecimal price;
    private Integer count;//number of items
    private Date createTime;
}
  • ②. Convert Product to ProductVo object, set the id attribute to a constant, set the default value of count to 1, and set productSn to UUID generation
@Data
public class ProductVo {<!-- -->
    //Use constants
    private Long id;
    //Use expression to generate attributes
    private String productSn;
    private String name;
    private String subTitle;
    private String brandName;
    private BigDecimal price;
    //use default value
    private Integer number;//number of items
    private Date createTime;
}
  • ③. Create the ProductMapper interface and set the mapping rules through constant, defaultValue, and expression in the @Mapping annotation;
@Mapper(imports = {<!-- -->UUID.class})
public interface ProductMapper {<!-- -->
    ProductMapper INSTANCE = Mappers.getMapper(ProductMapper.class);

    @Mapping(target = "id",constant = "-1L") //Set the id field of the converted productVo to constant -1
    @Mapping(source = "count",target = "number",defaultValue = "1")
    @Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
    ProductVo convertToVo(Product product);
}
  • ④. Test data
 @GetMapping("/defaultMapping")
    public Result defaultMapping() {<!-- -->
        Product product = new Product();
        product.setId(200L);
        product.setCount(null);
        ProductVo productVo = ProductMapper.INSTANCE.convertToVo(product);
        System.out.println(JSON.toJSONString(productVo));
        return Result.success(productVo);
    }

⑦. Customized aspect processing

  • ①. MapStruct also supports some custom operations before and after mapping, similar to aspects in Spring’s AOP

  • ②. At this time we need to create a custom processing method, create an abstract class ProductRoundMapper, customize the pre-mapping operation through the @BeforeMapping annotation, and customize the post-mapping operation through the @AfterMapping annotation

@Mapper(imports = {<!-- -->UUID.class},unmappedTargetPolicy = ReportingPolicy.IGNORE)
public abstract class ProductRoundMapper {<!-- -->
    public static ProductRoundMapper INSTANCE = Mappers.getMapper(ProductRoundMapper.class);

    @Mappings({<!-- -->
            @Mapping(target = "id",constant = "-1L"),
            @Mapping(source = "count",target = "number",defaultValue = "1"),
            @Mapping(target = "productSn",expression = "java(UUID.randomUUID().toString())")
    })
    public abstract ProductVo convertToVo(Product product);

    @BeforeMapping
    public void beforeMapping(Product product){<!-- -->
        //Set to 0 when price<0 before mapping
        if(product.getPrice().compareTo(BigDecimal.ZERO)<0){<!-- -->
            product.setPrice(BigDecimal.ZERO);
        }
    }

    @AfterMapping
    public void afterMapping(@MappingTarget ProductVo productVo){<!-- -->
        //Set the current time to createTime after mapping
        productVo.setCreateTime(new Date());
    }
}
  • ③. Test
 @GetMapping("/aspectMapping")
    public String defaultMapping() {<!-- -->
        Product product = new Product();
        product.setId(100L);
        product.setCount(null);
        product.setPrice(new BigDecimal(-100) );
        ProductVo productVo = ProductRoundMapper.INSTANCE.convertToVo(product);
        // {"createTime":1697113274023,"id":-1,"number":1,"price":0,"productSn":"fe154c52-8808-40e1- b0a6-68b5e6437ea5"}
        System.out.println(JSON.toJSONString(productVo));
        return JSON.toJSONString(productVo);
    }
  • ④. If you need to convert one List to another List, you can use this method
 result = xxxList.stream()
                    .map(XXX.INSTANCE::convertNew).collect(Collectors.toList());