天天看点

基于grpc从零开始搭建一个准生产分布式应用(6) - 01 - MapStruct基础

开始前必读:​​基于grpc从零开始搭建一个准生产分布式应用(0) - quickStart​​ 

从本章开始,大概会分5个章节来讲述mapStruct的使用。

一、Maven配置

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.22</version>
</dependency>

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

二、Maven-Plug配置

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <!--   使用lombok注意冲突,maven-compiler-plugin要3.6以上,lombok使用1.16.16版本以上,需要配置lombok的path   -->
        <!--   注意下面的声明顺序,否则lombok会不起作用   -->
        <annotationProcessorPaths>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.22</version>
            </path>
            <path>
                <groupId>org.mapstruct</groupId>
                <artifactId>mapstruct-processor</artifactId>
                <version>1.4.1.Final</version>
            </path>
            <path>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok-mapstruct-binding</artifactId>
                <version>0.1.0</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>      

2.1、打印编译日志

<!--配置showWarning和mapstruct.verbose=true会在编译时打印相关转换信息-->
<showWarnings>true</showWarnings>

<compilerArgs>
    <compilerArg>-Amapstruct.verbose=true </compilerArg>
</compilerArgs>      

效果如下

[INFO] -- MapStruct: selecting element mapping: com.mapstruct.bo.TestBO #testToBO(testPO).
[INFO] - MapStruct: creating bean mapping method implementation for com.mapstruct.bo.TestBO testToBO(com.mapstruct.bo.TestPO testPO).
[INFO] -- MapStruct: mapping property: testPO.getId() to: setId(java.lang.Long).
[INFO] -- MapStruct: selecting property mapping: testPO.getId().
[INFO] -- MapStruct: mapping property: testPO.getName() to: setName(java.lang.String).      

2.2、Spring声明方式

//需再增加以下参数在Maven配置上,
<compilerArgs>
    <compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg> <!--   以spring注入的方式访问mapper-->
</compilerArgs>

-------------------------------------------------------------------------------------
@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2022-01-31T00:38:47+0800",
    comments = "version: 1.4.1.Final, compiler: javac, environment: Java 1.8.0_144 (Oracle Corporation)"
)

/**这个东西是设置<compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg>后生成的,
 * 这个默认属性优先级低于Mapper注解上的componentModel属性值
 * */
@Component
public class ManyToOneImpl implements ManyToOne {
}      

2.3、Spring注入方式

//需再增加以下两个参数在Maven配置上,这个默认属性优先级低于Mapper注解上的injectionStrategy属性值
<compilerArgs>
    <!--   注入方式 默认是字段注入 设置为constructor是构造器注入-->
    <compilerArg> -Amapstruct.defaultInjectionStrategy=field </compilerArg>
</compilerArgs>      
@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, uses = {OneToOne.class})
public interface ManyToOne {
    List<TestBO> testToBOS(List<TestPO> testPOS);
}
@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface OneToOne {
    TestBO testToBO(TestPO testPO);
}
//=================以下是注入实现=================
public class ManyToOneImpl implements ManyToOne {
    @Autowired
    private OneToOne oneToOne;
}
//=================以下是构造函数方式,实现方式,但需要设置 injectionStrategy=================
@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS, uses = {OneToOne.class},
        injectionStrategy = InjectionStrategy.CONSTRUCTOR)
        
public class ManyToOneImpl implements ManyToOne {
    private final OneToOne oneToOne;

    @Autowired
    public ManyToOneImpl(OneToOne oneToOne) {
        this.oneToOne = oneToOne;
    }
}      

三、实现方式

3.1、通用复制

//这里假设BO和PO的类属性完全一样,
@Data
@ToString
public class TestPO {
    private Long id;
    private String name;
    private BigDecimal price;
    private Date creteTime;
}
//接口方式定义,如果用List时也必须定义单个属性的转换,在下例中testToBOS会用到testToBO方法,它们的方法名没有必然的联系,可随意定义,程序会自动组装;
@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) 或 @Mapper
public interface TestMapper {
    TestBO testToBO(TestPO testPO);
    List<TestBO> testToBOS(List<TestPO> testPOS);
}

//测试
@Test
public void baseTest() {
    List<TestPO> list = new ArrayList<>();
    for (int i = 0; i < 2; i++) {
        TestPO testPO = new TestPO();
        testPO.setId(1L);
        testPO.setName("haru");
        testPO.setPrice(new BigDecimal("12.02"));
        testPO.setCreteTime(new Date(System.currentTimeMillis()));
        list.add(testPO);
    }

    TestMapper mapper = Mappers.getMapper(TestMapper.class);  // 获取mapper
    List<TestBO> testBOS = mapper.testToBOS(list); // 转换
    System.out.println(testBOS);
}      

3.2、自定义映射(推荐)

3.2.1、接口实现

//在自动生成的类中会实现 此抽象类
@Mapper
public interface TestMapper {
    TestBO testToBO(TestPO testPO);
    List<TestBO> testToBOS(List<TestPO> testPOS);
}

TestMapper mapper = Mappers.getMapper(TestMapper.class);  // 获取mapper
List<TestBO> testBOS = mapper.testToBOS(list); // 转换      

3.2.2、抽象类-实现自定义方法

//作用同上面接口实现一样,在自动生成的类中会实现 此抽象类
@Mapper
public abstract class AbstractTestMapper {

    public TestBO testToBO(TestPO testPO) {
        TestBO testBO = new TestBO();
        testBO.setName(testPO.getName() + "BO");
        return testBO;
    }

    public abstract List<TestBO> testToBOS(List<TestPO> testPOS);
}

AbstractTestMapper mapper = Mappers.getMapper(AbstractTestMapper.class);  // 获取mapper
List<TestBO> testBOS = mapper.testToBOS(list); // 转换      

3.2.3、默认方法-(需jdk1.8)实现自定义方法

//适用1.8以上的JDK
@Mapper
public interface DefaultMappler {
    default TestBO testToBO(TestPO testPO) {
        TestBO testBO = new TestBO();
        testBO.setName(testPO.getName() + "BEEEO");
        return testBO;
    }
    List<TestBO> testToBOS(List<TestPO> testPOS);

}      

四、复杂使用

下面也是常用的使用方式,非高级用法。

4.1、多个对象映射成一个对象

public class TestPO {
    private Long id;
    private String name;
    private BigDecimal price;
    private Date creteTime;
}
public class TestTwoPO {
    private Long id;
    private String title;
    private Float totalPrice;
}
public class TestMixBO {
    private Long id;
    private String name;
    private BigDecimal price;
    private Date creteTime;
    private String title;
}

//这里需要注意的是,在上面的类都有一个ID属性,所以必须用Mapping指定一个soure,如source = "testPO.id",否则会报错
@Mapper
public interface ManyToOne {
    @Mapping(source = "testPO.id", target = "id")
    TestMixBO testToBO(TestPO testPO, TestTwoPO testTwoPO);
}

//void toTarget( MammalDto source, Long numberOfStomachs, @MappingTarget MammalEntity target );      

4.2、嵌套对象映射

可以将target设为".",source所对应的属性对象字段会全部映射到target中同名字段上。
@Data
@ToString
@Builder
public class TestMixBO {
    private Long id;
    private String name;
    private BigDecimal price;
    private Date creteTime;
    private String title;
    private float price1;
}

@Data
@ToString
public class TestThreePO {

    private TestPO test;
}
@Data
@ToString
public class TestPO {
    private Long id;
    private String name;
    private BigDecimal price;
    private Date creteTime;
}

@Mapper
public interface TestMapper {
    @Mapping(source = "test", target = ".")
    TestMixBO testToBO(TestThreePO testPO);
}      

4.3、非对象属性映射到目标上

@Mapper
public interface TestMapper {
    @Mapping(target = "discount", source = "totalPrice")
    TestMixBO testToBO(TestPO testPO, Float totalPrice);
 }
 //discount是TestMixBO新加的一个属性,这里的totalPrice就是一个自定义的参数,用于写死某个值时非常有用;      

4.4、更新数据

@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS)
public interface ManyToOne {
    //同样也可以使用@Mapping在方法上
    //@Mapping(target = "id", ignore = true)
    void updateBO(TestPO var1, @MappingTarget TestBO var2);
}

//生成的代码如下
public void updateBO(TestPO testPO, TestBO testBO) {
    if (testPO != null) {
        if (testPO.getId() != null) {
            testBO.setId(testPO.getId());
        } else {
            testBO.setId((Long)null);
}