mapstruct 對象映射詳解

前言

開發中,我們經常需要將PO轉DTO、DTO轉PO等一些實體間的轉換。

比較出名的有BeanUtil 和ModelMapper等,它們使用簡單,但是在稍顯複雜的業務場景下力不從心。

MapStruct這個插件可以用來處理domin實體類與model類的屬性映射,可配置性強。只需要定義一個 Mapper 接口,MapStruct 就會自動實現這個映射接口,避免了複雜繁瑣的映射實現。

參考文檔

MapStruct官網地址: http://mapstruct.org/

IDE 支持:https://mapstruct.org/documentation/ide-support

demo地址:https://github.com/herionZhang/mapstruct-demo

maven 依賴

	<dependency>
		<groupId>org.mapstruct</groupId>
		<artifactId>mapstruct</artifactId>
		<version>1.3.1.Final</version>
	</dependency>

	<dependency>
		<groupId>org.mapstruct</groupId>
		<artifactId>mapstruct-processor</artifactId>
		<version>1.3.1.Final</version>
	</dependency>

入門

不使用spring示例

測試對象準備

public class Car {
 
    private String make;
    private int numberOfSeats;
 
    //constructor, getters, setters etc.
}


public class CarDto {

private String make;
private int seatCount;

//constructor, getters, setters etc.
}

mapper定義

@Mapper
public interface CarMapper {
 	//爲客戶端提供對映射器實現的訪問。
    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class );
 
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car);
}

1、@Mapper項目編譯時會生產對應實現類

2、@Mapping 用來指定屬性映射的,如果兩個對象的屬性名相同是可以省略

編譯後源碼

package com.herion.example.demo.mappers;

import com.herion.example.demo.dto.CarDto;
import com.herion.example.demo.entity.Car;

import javax.annotation.Generated;

@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2019-11-27T16:03:01+0800",
comments = "version: 1.3.1.Final, compiler: javac, environment: Java 1.8.0_211 (Oracle Corporation)"
)
public class CarMapperImpl implements CarMapper {

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

CarDto carDto = new CarDto();

carDto.setSeatCount( car.getNumberOfSeats() );
carDto.setMake( car.getMake() );

return carDto;
}
}

源碼位置
在這裏插入圖片描述

測試類

package com.example.demo.mappers;

import com.example.demo.dto.CarDto;
import com.example.demo.entity.Car;
import org.junit.Test;



public class CarMapperTest {

    @Test
    public void shouldMapCarToDto() {
        //given
        Car car = new Car("Morris", 5);

        //when
        CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );

        System.out.println(carDto.toString());
    }
}

測試結果

CarDto(make=Morris, seatCount=5)

進階

以使用springboot爲示例,@Mapper(componentModel = “spring”),表示把當前Mapper類納入spring容器。

測試對象準備

package com.example.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Data
@NoArgsConstructor
public class User {
    private Long id;
    private String username;
    private String password;
    private String phoneNum;
    private String email;
    private Role role;
}



package com.example.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Role {
    private Long id;
    private String roleName;
    private String description;
}


package com.example.demo.dto;

import lombok.Data;

@Data
public class UserRoleDto {
    /**
     * 用戶id
     */
    private Long userId;
    /**
     * 用戶名
     */
    private String name;
    /**
     * 角色名
     */
    private String roleName;
}


package com.example.demo.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
    private Long id;
    private String name;
    private Boolean isDisable;

    private String email;

    private Date birthday;
}

對象屬性複製

定義mapper

package com.example.demo.mappers;


import com.example.demo.dto.UserRoleDto;
import com.example.demo.entity.Role;
import com.example.demo.entity.User;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.Mappings;


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

    /**
     * 對象屬性複製的方法
     *
     * @param user 這個參數就是源對象,也就是需要被複制的對象
     * @return 返回的是目標對象,就是最終的結果對象
     * @Mapping 用來定義屬性複製規則 source 指定源對象屬性 target指定目標對象屬性
     */
    @Mappings({
            @Mapping(source = "id", target = "userId"),
            @Mapping(source = "username", target = "name"),
            @Mapping(source = "role.roleName", target = "roleName")
    })
    UserRoleDto toUserRoleDto(User user);
}

測試類

package com.example.demo.mappers;

import com.example.demo.DemoApplication;
import com.example.demo.dto.UserRoleDto;
import com.example.demo.entity.Role;
import com.example.demo.entity.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
public class UserRoleMapperTest {

    // 注入Mapper
    @Autowired
    private UserRoleMapper userRoleMapper;

    Role role = null;
    User user = null;

    @Before
    public void before() {
        role = new Role(2L, "admin", "超級管理員");
        user = new User(1L, "herion", "123456", "1389999888", "[email protected]", role);
    }

    @Test
    public void toUserRoleDtorTest() {
        UserRoleDto userRoleDto=userRoleMapper.toUserRoleDto(user);
        System.out.println(userRoleDto);
    }
}

測試結果

UserRoleDto(userId=1, name=herion, roleName=admin)

多個參數中的值綁定

定義mapper

@Mappings({
            // 把user中的id綁定到目標對象的userId屬性中
            @Mapping(source = "user.id", target = "userId"),
            // 把user中的username綁定到目標對象的name屬性中
            @Mapping(source = "user.username", target = "name"),
            // 把role對象的roleName屬性值綁定到目標對象的roleName中
            @Mapping(source = "role.roleName", target = "roleName")
    })
    UserRoleDto toUserRoleDto(User user, Role role);

測試類

package com.example.demo.mappers;

import com.example.demo.DemoApplication;
import com.example.demo.dto.UserRoleDto;
import com.example.demo.entity.Role;
import com.example.demo.entity.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
public class UserRoleMapperTest {

    // 注入Mapper
    @Autowired
    private UserRoleMapper userRoleMapper;

    Role role = null;
    User user = null;

    @Before
    public void before() {
        role = new Role(2L, "admin", "超級管理員");
        user = new User(1L, "herion", "123456", "1389999888", "[email protected]", role);
    }

    @Test
    public void toUserRoleDto2Test() {
        UserRoleDto userRoleDto=userRoleMapper.toUserRoleDto(user,role);
        System.out.println(userRoleDto);
    }
}

測試結果

UserRoleDto(userId=1, name=herion, roleName=admin)

入參作爲值綁定

定義mapper

@Mappings({
            // 把user中的id綁定到目標對象的userId屬性中
            @Mapping(source = "user.id", target = "userId"),
            // 把user中的username綁定到目標對象的name屬性中
            @Mapping(source = "user.username", target = "name"),
            // 把role對象的roleName屬性值綁定到目標對象的roleName中
            @Mapping(source = "myRoleName", target = "roleName")
    })
    UserRoleDto useParameter(User user, String myRoleName);

測試類

package com.example.demo.mappers;

import com.example.demo.DemoApplication;
import com.example.demo.dto.UserRoleDto;
import com.example.demo.entity.Role;
import com.example.demo.entity.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
public class UserRoleMapperTest {

    // 注入Mapper
    @Autowired
    private UserRoleMapper userRoleMapper;

    Role role = null;
    User user = null;

    @Before
    public void before() {
        role = new Role(2L, "admin", "超級管理員");
        user = new User(1L, "herion", "123456", "1389999888", "[email protected]", role);
    }

   @Test
    public void useParameterTest() {
        UserRoleDto userRoleDto = userRoleMapper.useParameter(user, "myUserRole");
        System.out.println(userRoleDto);
    }
}

測試結果

UserRoleDto(userId=1, name=herion, roleName=myUserRole)

更新對象中的屬性

定義mapper

    @Mappings({
            @Mapping(source = "userId", target = "id"),
            @Mapping(source = "name", target = "username"),
            @Mapping(source = "roleName", target = "role.roleName")
    })
    void updateDto(UserRoleDto userRoleDto, @MappingTarget User user);

測試類

package com.example.demo.mappers;

import com.example.demo.DemoApplication;
import com.example.demo.dto.UserRoleDto;
import com.example.demo.entity.Role;
import com.example.demo.entity.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
public class UserRoleMapperTest {

    // 注入Mapper
    @Autowired
    private UserRoleMapper userRoleMapper;

    Role role = null;
    User user = null;

    @Before
    public void before() {
        role = new Role(2L, "admin", "超級管理員");
        user = new User(1L, "herion", "123456", "1389999888", "[email protected]", role);
    }

      @Test
    public void updateDtoTest() {
        UserRoleDto userRoleDto=new UserRoleDto();
        userRoleDto.setName("管理員");
        userRoleDto.setRoleName("架構部");
        userRoleDto.setUserId(2L);

        System.out.println("執行前 userRoleDto:"+userRoleDto.toString());
        System.out.println("執行前 user:"+user.toString());
        userRoleMapper.updateDto(userRoleDto,user);
        System.out.println("執行後 userRoleDto:"+userRoleDto.toString());
        System.out.println("執行後 user:"+user.toString());
    }
}

測試結果

執行前 userRoleDto:UserRoleDto(userId=2, name=管理員, roleName=架構部)
執行前 user:User(id=1, username=herion, password=123456, phoneNum=1389999888, email=123@qq.com, role=Role(id=2, roleName=admin, description=超級管理員))
執行後 userRoleDto:UserRoleDto(userId=2, name=管理員, roleName=架構部)
執行後 user:User(id=2, username=管理員, password=123456, phoneNum=1389999888, email=123@qq.com, role=Role(id=2, roleName=架構部, description=超級管理員))

自定義類型轉換

有時候,在對象轉換的時候可能會出現這樣一個問題,就是源對象中的類型是Boolean類型,而目標對象類型是String類型,這種情況可以通過@Mapper的uses屬性來實現:

定義規則類

package com.example.demo.format;

import org.springframework.stereotype.Component;

@Component
public class BooleanStrFormat {
    public String toStr(Boolean isDisable) {
        if (isDisable) {
            return "Y";
        } else {
            return "N";
        }
    }
    public Boolean toBoolean(String str) {
        if (str.equals("Y")) {
            return true;
        } else {
            return false;
        }
    }
}

定義Mapper

@Mapper( uses = { BooleanStrFormat.class}),注意,這裏的users屬性用於引用之前定義的轉換規則的類:

package com.example.demo.mappers;

import com.example.demo.dto.CustomerDto;
import com.example.demo.entity.Customer;
import com.example.demo.format.BooleanStrFormat;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;

import java.util.List;

@Mapper(componentModel = "spring", uses = { BooleanStrFormat.class})
public interface CustomerListMapper {

    @Mappings({
            @Mapping(source = "name", target = "customerName"),
            @Mapping(source = "isDisable", target = "disable")
    })
    CustomerDto customersToCustomerDto(Customer customer);
}

測試類

@SpringBootTest(classes = DemoApplication.class)
@RunWith(SpringRunner.class)
public class CustomerMapperTest {

    @Autowired
    private CustomerListMapper customerListMapper;


    @Test
    public void customersToCustomerDtoTest(){
        Customer customer = new Customer(1L, "herion",true,null,new Date());
        CustomerDto customerDto = customerListMapper.customersToCustomerDto(customer);
        System.out.println(customerDto.toString());
    }

}

測試結果

CustomerDto(id=1, customerName=herion, disable=Y)

list轉換

定義Mapper

@Mapper(componentModel = "spring", uses = { BooleanStrFormat.class})
public interface CustomerListMapper {

    @Mappings({
            @Mapping(source = "name", target = "customerName"),
            @Mapping(source = "isDisable", target = "disable")
    })
    CustomerDto customersToCustomerDto(Customer customer);


    List<CustomerDto> customersToCustomerDtos(List<Customer> customers);

}

測試類

    @Test
    public void customersToCustomerDtosTest(){
        Customer customer1 = new Customer(1L, "herion1",true,null,new Date());
        Customer customer2 = new Customer(2L, "herion2",true,null,new Date());
        Customer customer3 = new Customer(3L, "herion3",true,null,new Date());
        List<Customer> list=new ArrayList<Customer>();
        list.add(customer1);
        list.add(customer2);
        list.add(customer3);
        List<CustomerDto> customerDtos = customerListMapper.customersToCustomerDtos(list);
        customerDtos.forEach(customer -> {
            System.out.println(customer.toString());
        });
    }

測試結果:

CustomerDto(id=1, customerName=herion1, disable=Y)
CustomerDto(id=2, customerName=herion2, disable=Y)
CustomerDto(id=3, customerName=herion3, disable=Y)

複雜混合用法

定義vo

package com.example.demo.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class CustomerVO {
    private Long id;
    private String disable;
    private String email;
    private String birthDateFormat;
    private String username;
    private String phoneNum;
}

定義mapper

   @Mappings({
            @Mapping(source = "customer.name", target = "username"),
            @Mapping(source = "customer.id", target = "id"),
            //格式轉換
            @Mapping(source = "customer.isDisable", target = "disable"),
            //日期格式轉換
            @Mapping(source = "customer.birthday", target = "birthDateFormat", dateFormat = "yyyy-MM-dd HH:mm:ss"),
            //ignore 忽略映射
            @Mapping(target = "email", ignore = true)
    })
    CustomerVO userCustomerToCustomerVO(Customer customer, User user);

測試類

 @Test
 public void userCustomerToCustomerVOTest(){
     Customer customer = new Customer();
     customer.setId(111L);
     customer.setName("herion");
     customer.setIsDisable(true);
     customer.setBirthday(new Date());

     User user=new User();
     user.setEmail("[email protected]");
     user.setId(222L);
     user.setPhoneNum("13812344321");
     CustomerVO customerVO = customerMapper.userCustomerToCustomerVO(customer,user);
     System.out.println(customerVO.toString());
 }

測試結果

CustomerVO(id=111, disable=Y, email=null, birthDateFormat=2019-11-27 15:44:47, username=herion, phoneNum=13812344321)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章