In daily development, we always write a lot of code about PO to VO or VO to DTO, which causes our program to be unusually bloated. As follows:

    public static ParkinglotVO DTOcastToVO(ParkinglotDTO parkinglotDTO) {
        ParkinglotVO parkinglotVO = new ParkinglotVO();
        parkinglotVO.address = parkinglotDTO.getContent().getName().getAddress();
        parkinglotVO.distance = parkinglotDTO.getDistance().getValue();
        parkinglotVO.lat = parkinglotDTO.getContent().getName().getLat();
        parkinglotVO.lon = parkinglotDTO.getContent().getName().getLon();
        parkinglotVO.parkinglotId = parkinglotDTO.getContent().getName().getParkinglotId();
        parkinglotVO.isReservation = parkinglotDTO.getContent().getName().getIsReservation();
        parkinglotVO.parkinglotName = parkinglotDTO.getContent().getName().getParkinglotName();
        parkinglotVO.totalPlaces = parkinglotDTO.getContent().getName().getTotalPlaces();
        parkinglotVO.isSupportAgv = parkinglotDTO.getContent().getName().getIsSupportAgv();
        parkinglotVO.isSpaceSearch = parkinglotDTO.getContent().getName().getIsSpaceSearch();
        parkinglotVO.photo = parkinglotDTO.getContent().getName().getPhoto();
        parkinglotVO.photoMin = parkinglotDTO.getContent().getName().getPhotoMin();
        parkinglotVO.isSupportCharging = parkinglotDTO.getContent().getName().getIsSupportCharging();
        parkinglotVO.chargingNum = parkinglotDTO.getContent().getName().getChargingNum();
        parkinglotVO.isSub = parkinglotDTO.getContent().getName().getIsSub();
        parkinglotVO.isSupportNormal=parkinglotDTO.getContent().getName().getIsSupportNormal();
        return parkinglotVO;
    }
Copy the code

Writing this kind of code is time-consuming and nutritious, but it has to be written. Spring and Apache provide us with the BeatUtils tool, which can be accessed through

	public static void copyProperties(Object source, Object target) throws BeansException {
		copyProperties(source, target, null, (String[]) null);
	}
Copy the code

Realize the copy of properties between objects, but this way has obvious disadvantages, his implementation mechanism is through reflection, resulting in a long time to copy properties, performance is very low. The Alibaba Coding specification also avoids using BeanUtils as much as possible.

Using MapStruct

MapStruct solves this problem nicely.

MapStruct

Mapstruct.org/ github: github.com/mapstruct/m… Documents: mapstruct.org/documentati…

Using mapStruct

Make sure your JDK version is java8 or higher

Introduction of depend on

<properties>
	<mapstruct.version>1.3.1. The Final</mapstruct.version>
</properties>
<dependencies>
	<dependency>
		<groupId>org.mapstruct</groupId>
		<artifactId>mapstruct</artifactId>
		<version>${mapstruct.version}</version>
	</dependency>
	<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

Prepare several entity classes

  1. Person
@Data
public class Person {
    private Integer age;
    private String fullname;
}
Copy the code
  1. PersonDTO
@Data
public class PersonDTO {
    private Integer age;
    private String name;
}
Copy the code
  1. Engine
@Data
public class Engine {
    private Integer horsePower;
    private Integer fuel;
}
Copy the code
  1. EngineDTO
@Data
public class EngineDTO {
    private Integer horsePower;
    private Integer fuel;
}
Copy the code
  1. Car
@Data
public class Car {
    private Integer make;
    private Integer numberOfSeats;
    private Engine engine;
}
Copy the code
  1. CarDTO
@Data
public class CarDTO {
    private Integer manufacturer;
    private Integer seatCount;
    private EngineDTO engine;
    private PersonDTO person;
}
Copy the code

Define a mapper

Note that the annotations used here are all org.mapstruct and do not mislead.

@Mapper
public interface CarMap {

    @Mapping(source = "make", target = "manufacturer")
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDTO carToCarDto(Car car);

    @Mapping(source = "fullname", target = "name")
    PersonDTO personToPersonDto(Person person);
}
Copy the code

If your DTO is the same as the field name in the entity class, you just need to write the method signature, and you don’t need to write any code. This parameter is required if the parameter name is changed@MapingAnnotations,sourceIs the name of the original parameter,targetIs the parameter name of the converted class. When compiled, the implementation class is generated in the same directory as follows:

@Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-08-18T19:50:47+0800", comments = "version: Final, Compiler: JavAC, Environment: Java 1.8.0_241 (Oracle Corporation)")
public class CarMapImpl implements CarMap {

    @Override
    public CarDTO carToCarDto(Car car) {
        if ( car == null ) {
            return null;
        }

        CarDTO carDTO = new CarDTO();

        carDTO.setSeatCount( car.getNumberOfSeats() );
        carDTO.setManufacturer( car.getMake() );
        carDTO.setEngine( engineToEngineDTO( car.getEngine() ) );

        return carDTO;
    }

    @Override
    public PersonDTO personToPersonDto(Person person) {
        if ( person == null ) {
            return null;
        }

        PersonDTO personDTO = new PersonDTO();

        personDTO.setName( person.getFullname() );
        personDTO.setAge( person.getAge() );

        return personDTO;
    }

    protected EngineDTO engineToEngineDTO(Engine engine) {
        if ( engine == null ) {
            return null;
        }

        EngineDTO engineDTO = new EngineDTO();

        engineDTO.setHorsePower( engine.getHorsePower() );
        engineDTO.setFuel( engine.getFuel() );

        returnengineDTO; }}Copy the code

Mapping method for multiple source parameters

So in the DTO class, the property of CarDTO is equal to the property of Car plus the property of Person, and that’s not a problem with mapStruct because it allows you to pass multiple sources of data.

    @Mapping(source = "car.make", target = "manufacturer")
    @Mapping(source = "car.numberOfSeats", target = "seatCount")
    CarDTO carToCarDto(Car car,Person person);
Copy the code

Note that the Mapping we write in @mapping needs to specify the object, using the object name. Property name compiled result

    public CarDTO carToCarDto(Car car, Person person) {
        if ( car == null && person == null ) {
            return null;
        }
        CarDTO carDTO = new CarDTO();
        if( car ! =null ) {
            carDTO.setSeatCount( car.getNumberOfSeats() );
            carDTO.setManufacturer( car.getMake() );
            carDTO.setEngine( engineToEngineDTO( car.getEngine() ) );
        }
        if( person ! =null ) {
            carDTO.setPerson( personToPersonDto( person ) );
        }
        return carDTO;
    }
Copy the code

Add custom methods to the mapper

In some cases, you may need to manually implement a specific mapping from one type to another that MapStruct cannot generate. One way around this problem is to implement custom methods on another class, which is then used by MapStruct generated mapmers. Eg: Manually write the Person attribute mapping

@Mapper
public interface CarMap {

    @Mapping(source = "car.make", target = "manufacturer")
    @Mapping(source = "car.numberOfSeats", target = "seatCount")
    CarDTO carToCarDto(Car car,Person person);

   default PersonDTO personToPersonDto(Person person){
       if(Objects.isNull(person)){
           return null;
       }
       PersonDTO personDTO = new PersonDTO();
       personDTO.setName(person.getFullname());
       personDTO.setAge(person.getAge());
       returnpersonDTO; }}Copy the code

The compiled implementation class:

@Generated( value = "org.mapstruct.ap.MappingProcessor", date = "2020-08-18T20:05:05+0800", comments = "version: Final, Compiler: JavAC, Environment: Java 1.8.0_241 (Oracle Corporation)")
public class CarMapImpl implements CarMap {

    @Override
    public CarDTO carToCarDto(Car car, Person person) {
        if ( car == null && person == null ) {
            return null;
        }

        CarDTO carDTO = new CarDTO();

        if( car ! =null ) {
            carDTO.setSeatCount( car.getNumberOfSeats() );
            carDTO.setManufacturer( car.getMake() );
            carDTO.setEngine( engineToEngineDTO( car.getEngine() ) );
        }
        if( person ! =null ) {
            carDTO.setPerson( personToPersonDto( person ) );
        }

        return carDTO;
    }

    protected EngineDTO engineToEngineDTO(Engine engine) {
        if ( engine == null ) {
            return null;
        }

        EngineDTO engineDTO = new EngineDTO();

        engineDTO.setHorsePower( engine.getHorsePower() );
        engineDTO.setFuel( engine.getFuel() );

        returnengineDTO; }}Copy the code

Nested mappings

Change CarDTO to expose Engine properties directly in CarDTO, where Engine exists as an existing object in the original Car. We can manually specify field mappings using @Mapping

@Data
public class CarDTO {
    private Integer manufacturer;
    private Integer seatCount;
    private Integer horsePower;
    private Integer fuel;
// private EngineDTO engine;
    private PersonDTO person;
}
Copy the code

Specify the mapping as an object name. attribute

    @Mapping(source = "engine.horsePower", target = "horsePower")
    @Mapping(source = "engine.fuel", target = "fuel")
    @Mapping(source = "make", target = "manufacturer")
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDTO carToCarDto(Car car);
Copy the code

Compiled result

public CarDTO carToCarDto(Car car) {
        if ( car == null ) {
            return null;
        }

        CarDTO carDTO = new CarDTO();

        carDTO.setHorsePower( carEngineHorsePower( car ) );
        carDTO.setSeatCount( car.getNumberOfSeats() );
        carDTO.setFuel( carEngineFuel( car ) );
        carDTO.setManufacturer( car.getMake() );

        return carDTO;
    }

    private Integer carEngineHorsePower(Car car) {
        if ( car == null ) {
            return null;
        }
        Engine engine = car.getEngine();
        if ( engine == null ) {
            return null;
        }
        Integer horsePower = engine.getHorsePower();
        if ( horsePower == null ) {
            return null;
        }
        return horsePower;
    }

    private Integer carEngineFuel(Car car) {
        if ( car == null ) {
            return null;
        }
        Engine engine = car.getEngine();
        if ( engine == null ) {
            return null;
        }
        Integer fuel = engine.getFuel();
        if ( fuel == null ) {
            return null;
        }
        return fuel;
    }
Copy the code

Update an existing Bean instance

In some cases, the mapping you need does not create a new instance of the target type, but rather updates an existing instance of that type. You can achieve this mapping @mappingTarget by adding a parameter to the target object and marking it as

    @Mapping(target = "make", source = "manufacturer")
    @Mapping(target = "numberOfSeats", source = "seatCount")
    void updateCarFromDto(CarDTO carDTO,@MappingTarget Car car);
Copy the code

The compiled

@Override
    public void updateCarFromDto(CarDTO carDTO, Car car) {
        if ( carDTO == null ) {
            return;
        }

        car.setMake( carDTO.getManufacturer() );
        car.setNumberOfSeats( carDTO.getSeatCount() );
        if( carDTO.getEngine() ! =null ) {
            if ( car.getEngine() == null ) {
                car.setEngine( new Engine() );
            }
            engineDTOToEngine( carDTO.getEngine(), car.getEngine() );
        }
        else {
            car.setEngine( null); }}protected void engineDTOToEngine(EngineDTO engineDTO, Engine mappingTarget) {
        if ( engineDTO == null ) {
            return;
        }

        mappingTarget.setHorsePower( engineDTO.getHorsePower() );
        mappingTarget.setFuel( engineDTO.getFuel() );
    }
Copy the code

The code generated by the updateCarFromDto() method updates the passed instance with Car using the properties of the given CarDTO object. There may be only one parameter marked as the mapping target. Instead of void, you can also set the return type of the method to the type of the target parameter, which causes the generated implementation to update the passed mapping target and return it. This allows the mapping method to be invoked smoothly.

Using mapper

Direct use of

@Mapper
public interface CarMap {
    CarMap CAR_MAP = Mappers.getMapper(CarMap.class);
}
Copy the code

Integration of the Spring

Set componentModel = “Spring”, where you need to use directly via @resource injection

@Mapper(componentModel = "spring")
public interface CarMap {... }Copy the code

Data type conversion

Implicit type conversion

In many cases, MapStruct handles type conversions automatically. For example, if an attribute int is a type in the source Bean but String is a type in the target Bean, the generated code transparently performs the conversion Integer#parseInt(String) by calling String#valueOf(int) and String# parseInt(String), respectively.

  • Between all Java primitive data types and their corresponding wrapper types, such as between int and Integer, Boolean and Boolean generated code is a sense when converting a wrapper type into the corresponding primitive type, i.e., null checking will be performed.
  • Between all Java primitive number types and wrapper types, such as Integer between int and long or byte and.

Numbers, date formatting

New price,createTime attributes in Car

@Data
public class Car {
    private Integer make;
    private Integer numberOfSeats;
    private Engine engine;
    
    private Integer price;
    private LocalDateTime createTime;
}
Copy the code

Engine: Replace the setup type with BigDecimal

@Data
public class Engine {
    private BigDecimal horsePower;
    private Integer fuel;
}
Copy the code

The corresponding EngineDTO and CarDTO are as follows

@Data
public class EngineDTO {
    private String horsePower;
    private Integer fuel;
}
@Data
public class CarDTO {
    private Integer manufacturer;
    private Integer seatCount;
    private EngineDTO engine;
    private PersonDTO person;
    private String price;
    private String createTime;
}
Copy the code

For the setup, add a $prefix to price followed by two zeros. The setup is scientific and time formatted as YYYY-MM-DD HH: MM :ss

    @Mapping(target = "engine.horsePower", source = "engine.horsePower", numberFormat = "#.##E0")
    @Mapping(target = "price", source = "price", numberFormat = "$#.00")
    @Mapping(target = "createTime", dateFormat = "yyyy-MM-dd HH:mm:ss")
    @Mapping(source = "make", target = "manufacturer")
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDTO updateCarFromDto(Car car);
Copy the code

The compiled

public class CarMapImpl implements CarMap { @Override public CarDTO updateCarFromDto(Car car) { if ( car == null ) { return null; } CarDTO carDTO = new CarDTO(); carDTO.setEngine( engineToEngineDTO( car.getEngine() ) ); carDTO.setSeatCount( car.getNumberOfSeats() ); if ( car.getPrice() ! = null ) { carDTO.setPrice( new DecimalFormat( "$#.00" ).format( car.getPrice() ) ); } carDTO.setManufacturer( car.getMake() ); if ( car.getCreateTime() ! = null ) { carDTO.setCreateTime( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm:ss" ).format( car.getCreateTime() ) ); } return carDTO; } private DecimalFormat createDecimalFormat( String numberFormat ) { DecimalFormat df = new DecimalFormat( numberFormat ); df.setParseBigDecimal( true ); return df; } protected EngineDTO engineToEngineDTO(Engine engine) { if ( engine == null ) { return null; } EngineDTO engineDTO = new EngineDTO(); if ( engine.getHorsePower() ! = null ) { engineDTO.setHorsePower( createDecimalFormat( "#.##E0" ).format( engine.getHorsePower() ) ); } engineDTO.setFuel( engine.getFuel() ); return engineDTO; }}Copy the code

Map collections

@Mapper
public interface CarMapper {
    Set<String> integerSetToStringSet(Set<Integer> integers);
    List<CarDto> carsToCarDtos(List<Car> cars);
    CarDto carToCarDto(Car car);
}
Copy the code

Mapping the map

public interface SourceTargetMapper {
    @MapMapping(valueDateFormat = "dd.MM.yyyy")
    Map<String, String> longDateMapToStringStringMap(Map<Long, Date> source);
}
Copy the code

Using expressions

scenario

Sometimes you need to use your own methods to map attributes. For example, there is a map in VO, and the converted class stores the JSON string form of the map.

@Data
public class AddPassagewayParam {
    private HashMap<String,Object> accessMap;
}
Copy the code
@Data
public class SmsPassageway implements Serializable {
    // It is a string of characters
    private String accessMsg;
Copy the code

We need to map a map to JSON STR so we can use “expressions” to write this

@Mapper(componentModel = "spring")
public interface SmsPassagewayMap {

    @Mapping(target = "accessMsg", expression = "java(com.jd.icity.tools.JsonHelper.object2Json(addPassagewayParam.getAccessMap()))" )
    SmsPassageway addPassagewayParam2SmsPassageway(AddPassagewayParam addPassagewayParam);
}
Copy the code

Commonly used probably so much at ordinary times, if you need more full use, please refer to website mapstruct.org/documentati…