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
- Person
@Data
public class Person {
private Integer age;
private String fullname;
}
Copy the code
- PersonDTO
@Data
public class PersonDTO {
private Integer age;
private String name;
}
Copy the code
- Engine
@Data
public class Engine {
private Integer horsePower;
private Integer fuel;
}
Copy the code
- EngineDTO
@Data
public class EngineDTO {
private Integer horsePower;
private Integer fuel;
}
Copy the code
- Car
@Data
public class Car {
private Integer make;
private Integer numberOfSeats;
private Engine engine;
}
Copy the code
- 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@Maping
Annotations,source
Is the name of the original parameter,target
Is 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…