使用mapstruct做对象属性转换

发布时间 2023-10-11 17:29:05作者: BlogMemory

MapStruct是一个Java注解处理器,它可以自动生成类型安全的Java Bean映射代码。MapStruct的主要目的是简化Java Bean之间的映射,它通过注解来指定Java Bean之间的映射规则,然后自动生成映射代码。

在MapStruct中,有几个常用的注解:

  1. @Mapper:用于指定一个Java Bean映射器接口,该接口定义了Java Bean之间的映射规则。

  2. @Mapping:用于指定Java Bean属性之间的映射关系。

  3. @Mappings:用于指定多个@Mapping注解,可以将多个Java Bean属性之间的映射关系组合在一起。

  4. @InheritInverseConfiguration:用于指定一个反向映射规则,可以将目标Java Bean映射回源Java Bean。

  5. @MappingTarget:用于指定一个目标Java Bean对象,可以将源Java Bean映射到目标Java Bean对象中。

  6. @AfterMapping:用于指定一个方法,在Java Bean映射完成后调用该方法。

下面是一个简单的MapStruct示例,其中定义了一个Java Bean映射器接口,用于将SourceBean映射到TargetBean中:

@Mapper
public interface BeanMapper {

    @Mapping(source = "sourceField", target = "targetField")
    TargetBean map(SourceBean sourceBean);

}

在上面的示例中,@Mapper注解用于指定BeanMapper接口是一个Java Bean映射器接口。@Mapping注解用于指定sourceField属性映射到targetField属性。然后,MapStruct会自动生成BeanMapper接口的实现代码,用于将SourceBean映射到TargetBean中。

需要注意的是,MapStruct中的注解非常灵活,可以根据实际需求进行组合和使用。同时,MapStruct还提供了丰富的配置选项,可以进一步定制生成的Java Bean映射代码。

 

MapStruct 的主要优点:

  • 生成类型安全的代码,减少了手写代码的错误和冗余;
  • 支持多种映射策略,包括注解、XML 配置、Java SPI 等;
  • 支持复杂类型的映射,如集合、嵌套对象等;
  • 支持自定义转换器,可以自定义转换逻辑;
  • 易于集成到现有项目中。

MapStruct 的使用步骤:

  1. 定义源类型和目标类型的 Java bean;
  2. 在源类型和目标类型中添加注解;
  3. 创建一个 Mapper 接口,并使用 @Mapper 注解标记它;
  4. 在 Mapper 接口中定义映射方法,使用 @Mapping 注解标记映射关系;
  5. 在需要进行映射的地方,使用 Mapper 接口的实现类进行映射。

示例:

假设有一个 User 类和一个 UserDto 类,需要将它们进行映射。

User 类:

public class User {
    private Long id;
    private String name;
    private Integer age;
    // getter/setter 略
}

UserDto 类:

public class UserDto {
    private Long id;
    private String username;
    private Integer age;
    // getter/setter 略
}

Mapper 接口:

@Mapper
public interface UserMapper {
    UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
    
    @Mapping(source = "name", target = "username")
    UserDto userToUserDto(User user);
}

使用示例:

User user = new User();
user.setId(1L);
user.setName("Tom");
user.setAge(18);

UserDto userDto = UserMapper.INSTANCE.userToUserDto(user);

以上代码将会把 User 对象映射为 UserDto 对象,其中 User 的 name 属性会被映射到 UserDto 的 username 属性。

 

项目中使用示例:

1、引入依赖

<!--工具类:数据映射-->
<properties>
    <mapstruct.version>1.5.5.Final</mapstruct.version>
</properties>

<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>
</dependency>

2、自定义转换接口

import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;

import java.util.List;

/**
 * @since 2023/8/17
 */
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface ObjectConverter {

    /**
     * request to dto list
     *
     * @param list
     * @return
     */
    List<TargetObjectDTO> toDtoList(List<SourceObjectDTO> list);
}

3、接口调用

public class CustomerController {

    private final ObjectConverter converter;

    public ApiResponse<Boolean> saveBatch(@RequestBody List<SourceObjectDTO> list) {

        boolean result = converter.toDtoList(list);
        return ApiResponse.ok(null);
    }
}

 

@MappingTarget 注解:

在 MapStruct 中,@MappingTarget 注解用于指定目标对象,以便在映射过程中将源对象的值映射到目标对象中。该注解可以用于方法级别或参数级别,用于指定要映射的目标对象。

@MappingTarget 注解的使用方法如下:

  1. 方法级别使用:
@Mapper
public interface UserMapper {

    @MappingTarget User map(UserDto userDto, @MappingTarget User user);

}

在上面的代码中,@MappingTarget 注解用于指定目标对象 User,将 UserDto 对象的值映射到 User 对象中。

  1. 参数级别使用:
@Mapper
public interface UserMapper {

    void map(UserDto userDto, @MappingTarget User user);

}

在上面的代码中,@MappingTarget 注解用于指定目标对象 User,将 UserDto 对象的值映射到 User 对象中。

需要注意的是,@MappingTarget 注解只能用于可变对象,即可以修改其属性值的对象。如果目标对象是不可变对象,如 Java 中的 String 类型,则不能使用 @MappingTarget 注解。

另外,@MappingTarget 注解还可以与其他注解一起使用,如 @Mapping、@InheritInverseConfiguration 等,以实现更复杂的映射逻辑。

 

@AfterMapping注解:

MapStruct是一个Java bean映射框架,它可以自动生成类型安全的映射代码,从而简化了Java bean之间的映射操作。在MapStruct中,@AfterMapping是一个注解,它可以用于在映射完成后执行一些自定义代码。

@AfterMapping注解可以用在方法上,用于在映射完成后执行一些额外的逻辑。该注解可以接受一个或多个参数,其中最重要的参数是source和target,它们分别表示源对象和目标对象。例如:

@Mapper
public interface UserMapper {

    @Mapping(target = "fullName", source = "firstName")
    @AfterMapping
    default void setFullName(@MappingTarget UserDTO userDTO, User user) {
        userDTO.setFullName(user.getFirstName() + " " + user.getLastName());
    }

    UserDTO userToUserDTO(User user);
}

在上面的例子中,我们定义了一个名为setFullName的方法,并使用@AfterMapping注解将其标记为映射完成后执行的方法。该方法接受两个参数,分别是UserDTO和User,它们分别表示映射后的目标对象和源对象。在该方法中,我们将源对象的firstName和lastName属性拼接成一个fullName属性,并设置到目标对象中。

需要注意的是,@AfterMapping注解只能用于void类型的方法,并且该方法的参数必须与映射方法的参数一致(除了@MappingTarget注解的参数)。此外,@AfterMapping注解还可以接受一个qualifiedBy参数,用于指定一个条件,只有当该条件成立时才执行该方法。例如:

@Mapper
public interface UserMapper {

    @Mapping(target = "fullName", source = "firstName")
    @AfterMapping(qualifiedBy = { FullNameCondition.class })
    default void setFullName(@MappingTarget UserDTO userDTO, User user) {
        userDTO.setFullName(user.getFirstName() + " " + user.getLastName());
    }

    UserDTO userToUserDTO(User user);
}

public class FullNameCondition {

    public boolean shouldSetFullName(UserDTO userDTO, User user) {
        return user.getLastName() != null;
    }
}

在上面的例子中,我们定义了一个名为shouldSetFullName的方法,并将其标记为FullNameCondition类的静态方法。该方法接受两个参数,分别是UserDTO和User,它们分别表示映射后的目标对象和源对象。在该方法中,我们判断源对象的lastName属性是否为null,如果不为null,则返回true,表示应该执行setFullName方法。在@AfterMapping注解中,我们使用qualifiedBy参数指定了FullNameCondition.class,表示只有当FullNameCondition.shouldSetFullName方法返回true时才执行setFullName方法。

 

@Mappings注解:

在MapStruct中,@Mappings注解用于指定多个@Mapping注解。它可以用于以下两种情况:

  1. 映射多个属性

当源对象和目标对象之间有多个属性需要映射时,可以使用@Mappings注解来指定多个@Mapping注解。

例如:

@Mapper
public interface CarMapper {

    @Mappings({
        @Mapping(source = "make", target = "manufacturer"),
        @Mapping(source = "numberOfSeats", target = "seatCount")
    })
    CarDto carToCarDto(Car car);
}

在上面的例子中,我们使用@Mappings注解来指定两个@Mapping注解,将源对象的“make”属性映射到目标对象的“manufacturer”属性,将“numberOfSeats”属性映射到“seatCount”属性。

  1. 忽略某些属性

有时候,我们希望在映射过程中忽略某些属性,可以使用@Mappings注解来指定忽略的属性。

例如:

@Mapper
public interface CarMapper {

    @Mappings({
        @Mapping(source = "make", target = "manufacturer"),
        @Mapping(source = "numberOfSeats", target = "seatCount"),
        @Mapping(target = "id", ignore = true)
    })
    CarDto carToCarDto(Car car);
}

在上面的例子中,我们使用@Mappings注解来指定三个@Mapping注解,将源对象的“make”属性映射到目标对象的“manufacturer”属性,将“numberOfSeats”属性映射到“seatCount”属性,并忽略目标对象的“id”属性。

总之,@Mappings注解是一个非常有用的注解,可以帮助我们在MapStruct中指定多个属性映射和忽略某些属性。

 

@InheritInverseConfiguration注解:

在使用 MapStruct 进行对象映射时,有时候我们需要将一个对象的属性值映射到另一个对象的属性上,同时也需要将这两个对象的属性映射反过来。这时候就可以使用 MapStruct 中的 @InheritInverseConfiguration 注解来实现反向映射。

@InheritInverseConfiguration 注解的作用是继承反向映射的配置,它通常与 @Mapping 注解一起使用。当我们在一个映射方法上使用了 @Mapping 注解指定了源对象属性和目标对象属性的映射关系时,MapStruct 会自动生成一个反向映射方法,用于将目标对象的属性值映射回源对象中。而 @InheritInverseConfiguration 注解就是用来继承这个自动生成的反向映射方法的配置。

下面是一个使用 @InheritInverseConfiguration 注解的示例:

java复制代码
@Mapper
public interface UserMapper {

    @Mapping(source = "name", target = "username")
    UserDTO toUserDTO(User user);

    @InheritInverseConfiguration
    User toUser(UserDTO userDTO);
}

在上面的示例中,我们定义了一个 UserMapper 接口,其中包含两个方法:toUserDTO()toUser()toUserDTO() 方法用于将 User 对象映射为 UserDTO 对象,其中指定了 name 属性映射到 username 属性上。而 toUser() 方法则用于将 UserDTO 对象映射为 User 对象,并使用 @InheritInverseConfiguration 注解继承了自动生成的反向映射方法的配置。