首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >超详细 mapstruct 简化教程

超详细 mapstruct 简化教程

原创
作者头像
派大星在吗
发布2021-12-16 22:34:11
发布2021-12-16 22:34:11
3K0
举报
文章被收录于专栏:我的技术专刊我的技术专刊

mapstruct spring

MapStruct 结合spring使用,设定componentModel = "spring"即可,如下Mapper接口:

代码语言:txt
复制
@Mapper(componentModel = "spring")
代码语言:txt
复制
public interface CarDtoMapper{
代码语言:txt
复制
    Car dtoToEntity(CarDto dto);
代码语言:txt
复制
}

生成的映射代码如下,发现实现类上添加了@Component注解

代码语言:txt
复制
@Generated(
代码语言:txt
复制
    value = "org.mapstruct.ap.MappingProcessor",
代码语言:txt
复制
    date = "2021-04-26T11:02:50+0800",
代码语言:txt
复制
    comments = "version: 1.4.2.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-6.8.jar, environment: Java 1.8.0_211 (Oracle Corporation)"
代码语言:txt
复制
)
代码语言:txt
复制
@Component
代码语言:txt
复制
public class CarDtoMapperImpl implements CarDtoMapper {
代码语言:txt
复制
    @Override
代码语言:txt
复制
    public Car dtoToEntity(CarDto dto) {
代码语言:txt
复制
        if ( dto == null ) {
代码语言:txt
复制
            return null;
代码语言:txt
复制
        }
代码语言:txt
复制
        Car car = new Car();
代码语言:txt
复制
        car.setName( dto.getName() );
代码语言:txt
复制
        return car;
代码语言:txt
复制
    }
代码语言:txt
复制
}

mapstruct spring 使用的缺点

mapstruct结合spring,在使用方式上主要是需要编写接口文件和定义函数所带来编码工作量:

  1. 需要创建mapper接口文件,这个是mapstruct框架的必须要经历的过程,代码量增加
  2. Dto和Entity之间互相转换,需要在接口中添加一个方法,并且添加上InheritInverseConfiguration注解,如下
代码语言:txt
复制
@InheritInverseConfiguration(name = "dtoToEntity")    
代码语言:txt
复制
CarDto entityToDto(Car dto);
  1. service 中依赖多个mapper转换,增加构造函数注入个数
  2. 覆盖已有对象,需要添加如下map方法,如下
代码语言:txt
复制
Car dtoMapToEntity(CarDto dto, @MappingTarget Car car)

反向映射,同样需要添加如下方法

代码语言:txt
复制
CarDto entityMapToDto(Car dto, @MappingTarget CarDto car);

理想的映射工具

对于对象映射,有一种理想的使用方式,伪代码如下

代码语言:txt
复制
Car car = mapper.map(dto, Car.class);
代码语言:txt
复制
// or
代码语言:txt
复制
Car car = new Car();
代码语言:txt
复制
mapper.map(dto, car);
代码语言:txt
复制
//  反向映射
代码语言:txt
复制
CarDto dto = mapper.map(entity, CarDto.class);
代码语言:txt
复制
// or
代码语言:txt
复制
CarDto dto = new CarDto();
代码语言:txt
复制
mapper.map(entity, dto);

只使用mapper对象,就可以解决任何对象之间的映射。

mapstruct 官方解决方案: mapstruct-spring-extensions

官方地址如下: [https://github.com/mapstruct/mapstruct-spring-

extensions](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fmapstruct%2Fmapstruct-

spring-extensions)

其思路是使用spring 的 Converter接口,官方用法如下

代码语言:txt
复制
@Mapper(config = MapperSpringConfig.class)
代码语言:txt
复制
public interface CarMapper extends Converter<Car, CarDto> {
代码语言:txt
复制
    @Mapping(target = "seats", source = "seatConfiguration")
代码语言:txt
复制
    CarDto convert(Car car);
代码语言:txt
复制
}
代码语言:txt
复制
  @ComponentScan("org.mapstruct.extensions.spring")
代码语言:txt
复制
  @Component
代码语言:txt
复制
  static class AdditionalBeanConfiguration {
代码语言:txt
复制
    @Bean
代码语言:txt
复制
    ConfigurableConversionService getConversionService() {
代码语言:txt
复制
      return new DefaultConversionService();
代码语言:txt
复制
    }
代码语言:txt
复制
  }
代码语言:txt
复制
  @BeforeEach
代码语言:txt
复制
  void addMappersToConversionService() {
代码语言:txt
复制
    conversionService.addConverter(carMapper);
代码语言:txt
复制
    conversionService.addConverter(seatConfigurationMapper);
代码语言:txt
复制
    conversionService.addConverter(wheelMapper);
代码语言:txt
复制
    conversionService.addConverter(wheelsMapper);
代码语言:txt
复制
    conversionService.addConverter(wheelsDtoListMapper);
代码语言:txt
复制
  }
代码语言:txt
复制
  @Test
代码语言:txt
复制
  void shouldKnowAllMappers() {
代码语言:txt
复制
    then(conversionService.canConvert(Car.class, CarDto.class)).isTrue();
代码语言:txt
复制
    then(conversionService.canConvert(SeatConfiguration.class, SeatConfigurationDto.class)).isTrue();
代码语言:txt
复制
    then(conversionService.canConvert(Wheel.class, WheelDto.class)).isTrue();
代码语言:txt
复制
    then(conversionService.canConvert(Wheels.class, List.class)).isTrue();
代码语言:txt
复制
    then(conversionService.canConvert(List.class, Wheels.class)).isTrue();
代码语言:txt
复制
  }
代码语言:txt
复制
  @Test
代码语言:txt
复制
  void shouldMapAllAttributes() {
代码语言:txt
复制
    // Given
代码语言:txt
复制
    final Car car = new Car();
代码语言:txt
复制
    car.setMake(TEST_MAKE);
代码语言:txt
复制
    car.setType(TEST_CAR_TYPE);
代码语言:txt
复制
    final SeatConfiguration seatConfiguration = new SeatConfiguration();
代码语言:txt
复制
    seatConfiguration.setSeatMaterial(TEST_SEAT_MATERIAL);
代码语言:txt
复制
    seatConfiguration.setNumberOfSeats(TEST_NUMBER_OF_SEATS);
代码语言:txt
复制
    car.setSeatConfiguration(seatConfiguration);
代码语言:txt
复制
    final Wheels wheels = new Wheels();
代码语言:txt
复制
    final ArrayList<Wheel> wheelsList = new ArrayList<>();
代码语言:txt
复制
    final Wheel wheel = new Wheel();
代码语言:txt
复制
    wheel.setDiameter(TEST_DIAMETER);
代码语言:txt
复制
    wheel.setPosition(TEST_WHEEL_POSITION);
代码语言:txt
复制
    wheelsList.add(wheel);
代码语言:txt
复制
    wheels.setWheelsList(wheelsList);
代码语言:txt
复制
    car.setWheels(wheels);
代码语言:txt
复制
    // When
代码语言:txt
复制
    final CarDto mappedCar = conversionService.convert(car, CarDto.class);
代码语言:txt
复制
}

使用 mapstruct-spring-extensions,使用 ConfigurableConversionService

虽然解决了使用同一个对象映射,但是代码量没有解决,同时,没有提供覆盖已有对象的使用方式

推荐 mapstruct-spring-plus

地址: [https://github.com/ZhaoRd/mapstruct-spring-

plus](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FZhaoRd%2Fmapstruct-

spring-plus)

这个项目参考了`mapstruct-spring-

extensions项目,同时使用APT技术,动态生成Mapper接口,解决编写接口的问题,提供IObejctMapper`接口,提供所有的map方法。

maven引入
代码语言:txt
复制
<properties>
代码语言:txt
复制
    <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
代码语言:txt
复制
    <io.github.zhaord.version>1.0.1.RELEASE</io.github.zhaord.version>
代码语言:txt
复制
</properties>
代码语言:txt
复制
...
代码语言:txt
复制
<dependencies>
代码语言:txt
复制
    <dependency>
代码语言:txt
复制
        <groupId>org.mapstruct</groupId>
代码语言:txt
复制
        <artifactId>mapstruct</artifactId>
代码语言:txt
复制
        <version>${org.mapstruct.version}</version>
代码语言:txt
复制
    </dependency>
代码语言:txt
复制
    <dependency>
代码语言:txt
复制
        <groupId>io.github.zhaord</groupId>
代码语言:txt
复制
        <artifactId>mapstruct-spring-plus-boot-starter</artifactId>
代码语言:txt
复制
        <version>${io.github.zhaord.version}</version>
代码语言:txt
复制
    </dependency>
代码语言:txt
复制
</dependencies>
代码语言:txt
复制
...
代码语言:txt
复制
<build>
代码语言:txt
复制
    <plugins>
代码语言:txt
复制
        <plugin>
代码语言:txt
复制
            <groupId>org.apache.maven.plugins</groupId>
代码语言:txt
复制
            <artifactId>maven-compiler-plugin</artifactId>
代码语言:txt
复制
            <version>3.8.1</version>
代码语言:txt
复制
            <configuration>
代码语言:txt
复制
                <source>1.8</source>
代码语言:txt
复制
                <target>1.8</target>
代码语言:txt
复制
                <annotationProcessorPaths>
代码语言:txt
复制
                    <path>
代码语言:txt
复制
                        <groupId>org.mapstruct</groupId>
代码语言:txt
复制
                        <artifactId>mapstruct-processor</artifactId>
代码语言:txt
复制
                        <version>${org.mapstruct.version}</version>
代码语言:txt
复制
                    </path>
代码语言:txt
复制
                    <path>
代码语言:txt
复制
                        <groupId>io.github.zhaord</groupId>
代码语言:txt
复制
                        <artifactId>mapstruct-spring-plus-processor</artifactId>
代码语言:txt
复制
                        <version>${io.github.zhaord.version}</version>
代码语言:txt
复制
                    </path>
代码语言:txt
复制
                </annotationProcessorPaths>
代码语言:txt
复制
            </configuration>
代码语言:txt
复制
        </plugin>
代码语言:txt
复制
    </plugins>
代码语言:txt
复制
</build>
gradle 引入
代码语言:txt
复制
dependencies {
代码语言:txt
复制
    ...
代码语言:txt
复制
    compile 'org.mapstruct:mapstruct:1.4.2.Final'
代码语言:txt
复制
    compile 'io.github.zhaord:mapstruct-spring-plus-boot-starter:1.0.1.RELEASE'
代码语言:txt
复制
    annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
代码语言:txt
复制
    testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final' // if you are using mapstruct in test code
代码语言:txt
复制
    annotationProcessor 'io.github.zhaord:mapstruct-spring-plus-processor:1.0.1.RELEASE'
代码语言:txt
复制
    testAnnotationProcessor 'io.github.zhaord:mapstruct-spring-plus-processor:1.0.1.RELEASE' // if you are using mapstruct in test code
代码语言:txt
复制
    ...
代码语言:txt
复制
}
使用案例

使用代码如下

代码语言:txt
复制
public enum CarType {
代码语言:txt
复制
    SPORTS, OTHER
代码语言:txt
复制
}
代码语言:txt
复制
@Data
代码语言:txt
复制
public class Car {
代码语言:txt
复制
    private String make;
代码语言:txt
复制
    private CarType type;
代码语言:txt
复制
}
代码语言:txt
复制
@Data
代码语言:txt
复制
@AutoMap(targetType = Car.class)
代码语言:txt
复制
public class CarDto {
代码语言:txt
复制
    private String make;
代码语言:txt
复制
    private String type;
代码语言:txt
复制
}
代码语言:txt
复制
@ExtendWith(SpringExtension.class)
代码语言:txt
复制
@ContextConfiguration(
代码语言:txt
复制
        classes = {AutoMapTests.AutoMapTestConfiguration.class})
代码语言:txt
复制
public class AutoMapTests {
代码语言:txt
复制
    @Autowired
代码语言:txt
复制
    private IObjectMapper mapper;
代码语言:txt
复制
    @Test
代码语言:txt
复制
    public void testDtoToEntity() {
代码语言:txt
复制
        var dto = new CarDto();
代码语言:txt
复制
        dto.setMake("M1");
代码语言:txt
复制
        dto.setType("OTHER");
代码语言:txt
复制
        Car entity = mapper.map(dto, Car.class);
代码语言:txt
复制
        assertThat(entity).isNotNull();
代码语言:txt
复制
        assertThat(entity.getMake()).isEqualTo("M1");
代码语言:txt
复制
        assertThat(entity.getCarType()).isEqualTo("OTHER");
代码语言:txt
复制
    }
代码语言:txt
复制
    @ComponentScan("io.github.zhaord.mapstruct.plus")
代码语言:txt
复制
    @Configuration
代码语言:txt
复制
    @Component
代码语言:txt
复制
    static class AutoMapTestConfiguration {
代码语言:txt
复制
    }
代码语言:txt
复制
}
AutoMap 生成的接口代码
代码语言:txt
复制
@Mapper(
代码语言:txt
复制
    config = AutoMapSpringConfig.class,
代码语言:txt
复制
    uses = {}
代码语言:txt
复制
)
代码语言:txt
复制
public interface CarDtoToCarMapper extends BaseAutoMapper<CarDto, Car> {
代码语言:txt
复制
  @Override
代码语言:txt
复制
  @Mapping(
代码语言:txt
复制
      ignore = false
代码语言:txt
复制
  )
代码语言:txt
复制
  Car map(final CarDto source);
代码语言:txt
复制
  @Override
代码语言:txt
复制
  @Mapping(
代码语言:txt
复制
      ignore = false
代码语言:txt
复制
  )
代码语言:txt
复制
  Car mapTarget(final CarDto source, @MappingTarget final Car target);
代码语言:txt
复制
}

mapstruct-spring-plus 带来的便捷

  1. 使用AutoMap注解,减少了重复代码的编写,尤其是接口文件和映射方法
  2. 依赖注入,只需要注入IObjectMapper接口即可,具体实现细节和调用方法,对客户端友好
  3. 没有丢失mapstruct的功能和效率
  4. @Mapping注解,都可以使用@AutoMapField来完成字段的映射设置,因为@AutoMapField继承自@Mapping,比如字段名称不一致、跳过映射等

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • mapstruct spring
  • mapstruct spring 使用的缺点
  • 理想的映射工具
  • mapstruct 官方解决方案: mapstruct-spring-extensions
  • 推荐 mapstruct-spring-plus
    • maven引入
    • gradle 引入
    • 使用案例
    • AutoMap 生成的接口代码
  • mapstruct-spring-plus 带来的便捷
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档