属性映射工具——MapStruct(三)

目录:

属性映射工具——MapStruct(一)

属性映射工具——MapStruct(二)

属性映射工具——MapStruct(三)

属性映射工具——MapStruct(四)

属性映射工具——MapStruct(五)


 

好,我们继续吧,这是MapStruct系列的第三篇。今天我们讲的是MapStruct的高级语法。

一、常用注解解释

1.1、@Mapper——表示该接口作为映射接口,编译时MapStruct处理器的入口

  1)uese:外部引入的转换类;

  2)componentModel:就是依赖注入,类似于在spring的servie层用@servie注入,那么在其他地方可以使用@Autowired取到值。该属性可取的值为

    a)默认:这个就是经常使用的 xxxMapper.INSTANCE.xxx;

    b)cdi:使用该属性,则在其他地方可以使用@Inject取到值;

    c)spring:使用该属性,则在其他地方可以使用@Autowired取到值;

    d)jsr330/Singleton:使用者两个属性,可以再其他地方使用@Inject取到值;

1.2、@Mappings——一组映射关系,值为一个数组,元素为@Mapping

1.3、@Mapping——一对映射关系

  1)target:目标属性,赋值的过程是把“源属性”赋值给“目标属性”;

  2)source:源属性,赋值的过程是把“源属性”赋值给“目标属性”;

  3)dateFormat:用于源属性是Date,转化为String;

  4)numberFormat:用户数值类型与String类型之间的转化;

  5)constant:不管源属性,直接将“目标属性”置为常亮;

  6)expression:使用表达式进行属性之间的转化;

  7)ignore:忽略某个属性的赋值;

  8)qualifiedByName:根据自定义的方法进行赋值;

  9)defaultValue:默认值;

1.4、@MappingTarget——用在方法参数的前面。使用此注解,源对象同时也会作为目标对象,用于更新。

1.5、@InheritConfiguration——指定映射方法

1.6、@InheritInverseConfiguration——表示方法继承相应的反向方法的反向配置

1.7、@Named——定义类/方法的名称

 

二、mapStruct高级语法1

2.0、示例准备

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student1 {
    //主键
    private long id;
    //编号
    private String no;
    //名称
    private String name1;
    //年龄
    private String age1;
    //生日
    private Date birthday1;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student2{
    //主键
    private long id;
    //编号
    private String no;
    //名称
    private String name2;
    //年龄
    private Integer age2;
    //生日
    private String birthday2;
    //地址(市)
    private String addresCity;
}

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
    //
    private String province;
    //
    private String city;
    //
    private String county;
    //街道
    private String street;
}
View Code

2.1、使用抽象类代替接口

  之前我们使用的是接口来实现属性的转化,编译过后自动生成该接口的实现类。实际上也可以使用“抽象类”实现属性的转化,编译过后自动生成该抽象类的子类。当然此时抽象类里面的方法当然是抽象方法了。映射器的形式有所变化,但是大部分是相似的。提到映射器,这里多说一句:MapStruct生成的映射器是线程安全的,因此可以安全地从多个线程同时访问它们。以下的所有例子都是基于“抽象类”的,所以这里再不举例了。

2.2、多参数赋值

  这个就是多参数赋值,需要注意的是,此时source的值需要加上参数的前缀,用于区分。还有这里我们使用了抽象类代替了之前的接口。

/**
* Student2(id=0, no=null, name2=张三, age2=null, birthday2=null, addresCity=安庆市)
*/
@Test
public void test1(){
    Student1 student1 = new Student1();
    student1.setName1("张三");
    Address address = new Address("安徽省","安庆市","太湖县","mapStruct街道");
    Student2 student2 = StudentMapper.INSTANCE.toStudent2(student1,address);
    System.out.println(student2);
}
@Mapper
public abstract class StudentMapper {
    public static final StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mappings({
            @Mapping(source = "address.city",target = "addresCity"),
            @Mapping(source = "student1.name1",target = "name2")
    })
    abstract Student2 toStudent2(Student1 student1,Address address);
}

2.3、直接使用参数作为属性值

/**
* Student2(id=0, no=null, name2=null, age2=null, birthday2=null, addresCity=天津市)
*/
@Test
public void test3(){
    Student1 student1 = new Student1();
    student1.setName1("王五");
    Student2 student2 = StudentMapper.INSTANCE.toStudent2(student1,"天津市");
    System.out.println(student2);
}
@Mapper
public abstract class StudentMapper {
    public static final StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
@Mappings({ @Mapping(source
= "city",target = "addresCity"), }) abstract Student2 toStudent2(Student1 student1,String city); }

2.4、更新对象属性

  之前我们的转化的方法都是有返回值的,比如将A赋值给B,那么转化的方法参数是A,返回类型为B。但是现在,我要说,转化方法可以使无返回值,比如将A赋值给B,采用此方法,方法的参数是A和B,只是需要在参数B前面加 @MappingTarget。类似与对B的更新,如下:

/**
     * 转化前Student2(id=0, no=null, name2=null, age2=null, birthday2=null, addresCity=北京市)
     * 转化后Student2(id=0, no=null, name2=王五, age2=null, birthday2=null, addresCity=北京市)
     */
    @Test
    public void test2(){
        Student2 student2 = new Student2();
        student2.setAddresCity("北京市");
        System.out.println("转化前"+student2);
        Student1 student1 = new Student1();
        student1.setName1("王五");
        StudentMapper.INSTANCE.updateStudent2(student1,student2);
        System.out.println("转化后"+student2);
    }
@Mapper
public abstract class StudentMapper {
    public static final StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mapping(source = "name1",target = "name2")
    abstract void updateStudent2(Student1 student1, @MappingTarget Student2 student2);
}

 

三、mapStruct高级语法2

3.0、示例准备

@Data
@NoArgsConstructor
@AllArgsConstructor
class Student1 {
    //主键
    private Long id;
    //名称
    private String name1;
    //年龄
    private String age1;
    //创建时间
    private Date createTime1;
    //体重
    private Double weight1;
    //身高
    private String bloodType1;
    //性别
    private SexEnum sex1;
}


@Data
@NoArgsConstructor
@AllArgsConstructor
class Student2 {
    //主键
    private Long id;
    //名称
    private String name2;
    //年龄
    private Integer age2;
    //创建时间
    private String createTime2;
    //体重
    private String weight2;
    //身高
    private String bloodType2;
    //性别(0:女,1:男)
    private Integer sex2;
}

enum SexEnum {
    MAN(1, "男"),
    WOMAN(0, "女");

    @Setter
    @Getter
    private Integer code;
    @Setter
    @Getter
    private String desc;

    SexEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}
View Code

3.1、映射反转

   我们首先看这样一个例子,在实际的项目中,经常是需要将A ——> B,还有就是将B——> A。根据我们目前所学到的mapStruct知识,我们是这样写的。以下是一个示例。

      

  不知你注意到了没,写的很繁琐,很累赘。实际上在people1转化为people2的时候,我规则都配置了(下文称:原映射);而people2转people的时候,这些规则又写了一遍(下文称:新映射),很明显原映射与新映射是一对互逆的操作,所以如果程序智能一点,在原映射存在的前提下,people2转people1的时候应该我就不用配置了,或者基于原映射稍微修改就好了,这是最理想的状况。那mapStruct能做到吗?能,当然能,不然mapStruct怎么能说强大呢,我们需要使用@InheritInverseConfiguration。它能满足我们的需求。该注解需要注意三点:

1)有一个注解@InheritConfiguration(下文提到)与该注解长得很像,请注意区分,虽然他们的功能有点相似,但是他们是不同的注解;
2)该注解虽然可以实现反转赋值,但是有一种情况需要手动加强——原映射的Mapping中没有source(典型的使用了expression、constant属性的都属于)。对于有这种情况的属性,原映射与新映射都需要指定出来;

3)该注解只有一个属性name,它的值是原映射名。你可以把它理解为原映射对应的方法的名字,即方法的名字就是映射名。如上面的例子,规则名是"toPeople2"。那该注解可以这样写@InheritInverseConfiguration(name="toPeople2")。

  

      下面是我准备的一个例子,有三点需要注意:

1)可以看到由于age的转化中使用了expression、bloodType转化的过程中使用了constant。这两个Mapping都没有source。所以我们在使用了@InheritInverseConfiguration注解的同时,又使用了@Mapping对这两个属性做了单独的加强,不然会报错;

2)sex属性,该属性是一个枚举,在原映射中我们使用了qualifiedByName = "getBySexEnum"指定了对应的转化方法,但是新映射中我们没有指定mapStruct是怎么知道的呢,我们看生成的实现方法,新映射里面使用了getByCode。mapStruct怎么知道要用getByCode的呢——猜的。确实是猜的,它的思想是,在方法里面找参数、返回值都符合的方法。本例中,我们要将Student2.sex2(Integer类型) 赋值给 Student1.sex(SexEnum型),mapStruct就找参数为Integer,返回值为SexEnum的方法——只有getByCode符合,所以就用该方法转化了sex属性。讲到这里,是不是有疑问了,那我定义多个方法:参数相同、返回值相同,就是方法名不同,mapStruct怎么识别用哪个方法呢。这个要不你就特殊指定,如果不指定,会编译错误!!所以尽量定义的方法具有“唯一性”,使mapStruct能智能识别。

3)mapping的expression属性,应该是我第一次用到,它的值就是一个java代码,只不过在书写的时候注意,需要使用java(我是java代码)包起来,使用String,Interger之类的尽量使用全路径。但是如果这个java代码会抛异常,那可能不能使用expression了,需要自定义方法然后用qualifyByName引用。另外需要注意的是,这里我是对age采用了expression,实际上这里可以自动隐式类型转化的,没必要使用expression,我用的目的只是为了说明问题。

@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mappings({
            @Mapping(source = "name1", target = "name2",defaultValue = "某某人"),
            @Mapping(target = "age2", expression = "java(java.lang.Integer.valueOf(student1.getAge1()))"),
            @Mapping(source = "createTime1", target = "createTime2", dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"),
            @Mapping(source = "weight1", target = "weight2", numberFormat = "0.00"),
            @Mapping(target = "bloodType2", constant = "A 型血"),
            @Mapping(source = "sex1",target = "sex2",qualifiedByName = {"getBySexEnum"})
    })
    Student2 toStudent2(Student1 student1);

    @InheritInverseConfiguration(name = "toStudent2")
    @Mappings({
            @Mapping(target = "age1", expression = "java(java.lang.String.valueOf(student2.getAge2()))"),
            @Mapping(target = "bloodType1", constant = "YY 型血")
    })
    Student1 toStudent1(Student2 student2);
  //==========================================================================================
    @Named("getByCode")
    default SexEnum getByCode(Integer code){
        SexEnum[] sexEnums = SexEnum.values();
        for (SexEnum item:sexEnums){
            if(item.getCode().equals(code)){
                return item;
            }
        }
        return null;
    }

    @Named("getBySexEnum")
    default Integer getBySexEnum(SexEnum sexEnum){
        return sexEnum.getCode();
    }
}

对应的生成的实现类

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-07-24T16:19:37+0800",
    comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class StudentMapperImpl implements StudentMapper {
    @Override
    public Student2 toStudent2(Student1 student1) {
        if ( student1 == null ) {
            return null;
        }
        Student2 student2 = new Student2();
        student2.setSex2( getBySexEnum( student1.getSex1() ) );
        if ( student1.getName1() != null ) {
            student2.setName2( student1.getName1() );
        }
        else {
            student2.setName2( "某某人" );
        }
        if ( student1.getCreateTime1() != null ) {
            student2.setCreateTime2( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).format( student1.getCreateTime1() ) );
        }
        if ( student1.getWeight1() != null ) {
            student2.setWeight2( new DecimalFormat( "0.00" ).format( student1.getWeight1() ) );
        }
        student2.setId( student1.getId() );
        student2.setBloodType2( "A 型血" );
        student2.setAge2( java.lang.Integer.valueOf(student1.getAge1()) );
        return student2;
    }


    @Override
    public Student1 toStudent1(Student2 student2) {
        if ( student2 == null ) {
            return null;
        }
        Student1 student1 = new Student1();
        try {
            if ( student2.getCreateTime2() != null ) {
                student1.setCreateTime1( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).parse( student2.getCreateTime2() ) );
            }
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
        student1.setName1( student2.getName2() );
        student1.setSex1( getByCode( student2.getSex2() ) );
        try {
            if ( student2.getWeight2() != null ) {
                student1.setWeight1( new DecimalFormat( "0.00" ).parse( student2.getWeight2() ).doubleValue() );
            }
        }
        catch ( ParseException e ) {
            throw new RuntimeException( e );
        }
        student1.setId( student2.getId() );
        student1.setBloodType1( "YY 型血" );
        student1.setAge1( java.lang.String.valueOf(student2.getAge2()) );
        return student1;
    }
}

3.2、使用已有的映射更新对象属性

  我感觉上面3.1已经叙述的够详细了,这里我就不再累述了。只不过是使用的@InheritConfiguration注解。附上示例代码。

@Mapper
public interface StudentMapper {
    StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);

    @Mappings({
            @Mapping(source = "name1", target = "name2",defaultValue = "某某人"),
            @Mapping(target = "age2", expression = "java(java.lang.Integer.valueOf(student1.getAge1()))"),
            @Mapping(source = "createTime1", target = "createTime2", dateFormat = "yyyy-MM-dd HH:mm:ss:SSS"),
            @Mapping(source = "weight1", target = "weight2", numberFormat = "0.00"),
            @Mapping(target = "bloodType2", constant = "A 型血"),
            @Mapping(source = "sex1",target = "sex2",qualifiedByName = {"getBySexEnum"})
    })
    Student2 toStudent2(Student1 student1);

    @InheritConfiguration(name = "toStudent2")
    void updateStudent2(Student1 student1, @MappingTarget Student2 student2);

    @Named("getBySexEnum")
    default Integer getBySexEnum(SexEnum sexEnum){
        return sexEnum.getCode();
    }
}

 

 对应的生成的实现类:

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-07-24T16:19:37+0800",
    comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class StudentMapperImpl implements StudentMapper {
    @Override
    public Student2 toStudent2(Student1 student1) {
        if ( student1 == null ) {
            return null;
        }

        Student2 student2 = new Student2();
        student2.setSex2( getBySexEnum( student1.getSex1() ) );
        if ( student1.getName1() != null ) {
            student2.setName2( student1.getName1() );
        }
        else {
            student2.setName2( "某某人" );
        }
        if ( student1.getCreateTime1() != null ) {
            student2.setCreateTime2( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).format( student1.getCreateTime1() ) );
        }
        if ( student1.getWeight1() != null ) {
            student2.setWeight2( new DecimalFormat( "0.00" ).format( student1.getWeight1() ) );
        }
        student2.setId( student1.getId() );
        student2.setBloodType2( "A 型血" );
        student2.setAge2( java.lang.Integer.valueOf(student1.getAge1()) );
        return student2;
    }

    @Override
    public void updateStudent2(Student1 student1, Student2 student2) {
        if ( student1 == null ) {
            return;
        }
        student2.setSex2( getBySexEnum( student1.getSex1() ) );
        if ( student1.getName1() != null ) {
            student2.setName2( student1.getName1() );
        }
        else {
            student2.setName2( "某某人" );
        }
        if ( student1.getCreateTime1() != null ) {
            student2.setCreateTime2( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).format( student1.getCreateTime1() ) );
        }
        if ( student1.getWeight1() != null ) {
            student2.setWeight2( new DecimalFormat( "0.00" ).format( student1.getWeight1() ) );
        }
        student2.setId( student1.getId() );
        student2.setBloodType2( "A 型血" );
        student2.setAge2( java.lang.Integer.valueOf(student1.getAge1()) );
    }
}

3.3、使用Spring依赖注入

   之前我们是通过  StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class) 类似这种形式获取映射器对象的。但是如果你采用的Spring框架,你也可以通过依赖注入来获取映射器对象。你需要做的就是制定@Mapper注解的componentModel属性为spring,然后用@Autowired注入。有条件的情况下,强烈建议采用这种形式获取映射器,因为这样的映射器是单例的。

@Mapper(componentModel = "spring")
public interface CarMapper {
    CarDto carToCarDto(Car car);
}


@Autowired
private CarMapper mapper;

 

 

 

 

posted @ 2020-07-24 17:06  Erneste  阅读(10706)  评论(0编辑  收藏  举报