简介
MapStruct是满足JSR269规范的一个Java注解处理器,用于为Java Bean生成类型安全且高性能的映射。它基于编译阶段生成get/set代码,此实现过程中没有反射,不会造成额外的性能损失。 您所要做的就是定义一个mapper接口(@Mapper),该接口用于声明所有必须的映射方法。在编译期间MapStruct会为该接口自动生成实现类。该实现类使用简单的Java方法调用来映射source-target对象,在此过程中没有反射或类似的行为发生。 编译时期使用自动生成get和set方法,进行两个值得赋值
优点
与手工编写映射代码相比:
MapStruct通过生成冗长且容易出错的代码来节省时间。 与动态映射框架相比:
效率更高:使用简单的Java方法调用代替反射; 编译时类型安全:只能映射相同名称或带映射标记的属性; 编译时产生错误报告:如果映射不完整(存在未被映射的目标属性)或映射不正确(找不到合适的映射方法或类型转换)则会在编译时抛出异常。
相比于常见转换方法的有点
相比于getter/setter方法,省去代码 相比于BeanUtil.copyPropertie进行反射属性赋值,BeanUtil坑巨多,比如sources与target写反,难以定位某个字段在哪里进行的赋值,同时因为用到反射,导致性能也不佳。
坑点(后续实战不断补充)
如果项目中也同时使用到了 Lombok,一定要注意Lombok的版本要等于或者高于1.18.10,否则会有编译不通过的情况发生
实战
< ! -- mapStruct-- >
< 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>
基本使用
相同名称,不同类型
代码
@ToString
@Data
public class PersonVo {
private String name;
private Long age;
}
@Data
@ToString
@Accessors ( chain = true )
public class Person {
private String name;
private Integer age;
}
转换类,只需要声明接口,接口上面注明@Mapper标签即可
public interface BaseMapping < S, T> {
T sourceToTarget ( S source) ;
}
@Mapper
public interface PersonMapper extends BaseMapping < Person, PersonVo> {
}
public class PersonTest {
public static void main ( String[ ] args) {
PersonMapper mapper = Mappers. getMapper ( PersonMapper. class ) ;
Person person = new Person ( ) . setAge ( 10 ) . setName ( "lisi" ) ;
PersonVo personVo = mapper. sourceToTarget ( person) ;
System. out. println ( personVo) ;
}
}
结果
字段类型不一致,且不可以自动转换:
只要字段名称相等,不区分大小写,且类型可以自动转换的,可以直接进行转换,如果类型不同,则报找不到此set方法,下面的错误是将PersonVo的age改为date
public class PersonVo {
private String name;
private Date age;
}
public class Person {
private String name;
private Integer age;
}
Exception in thread "main" java. lang. NoSuchMethodError: org. vo. PersonVo. setAge ( Ljava/ lang/ Long; ) V
at org. convert. PersonMapperImpl. sourceToTarget ( PersonMapperImpl. java: 24 )
at org. convert. PersonMapperImpl. sourceToTarget ( PersonMapperImpl. java: 7 )
at org. test. PersonTest. main ( PersonTest. java: 13 )
字段名称不一致:
修改PersonVo中的age名称为age1,报下面错误,可以猜测出内部原理,使用Person的字段名称,调用PersonVo此字段名称的set方法,如果不存在则报错
public class PersonVo {
private String name;
private Integer age;
}
public class Person {
private String name;
private Integer age1;
}
Exception in thread "main" java. lang. NoSuchMethodError: org. vo. PersonVo. setAge ( Ljava/ lang/ Long; ) V
at org. convert. PersonMapperImpl. sourceToTarget ( PersonMapperImpl. java: 24 )
at org. convert. PersonMapperImpl. sourceToTarget ( PersonMapperImpl. java: 7 )
at org. test. PersonTest. main ( PersonTest. java: 13 )
解决方案
修改名称 使用@Mapping实现不同名称之间的映射 public class PersonVo {
private String name;
private Integer age;
}
public class Person {
private String name;
private Integer age1;
}
@Mapper
public interface PersonMapper extends BaseMapping < Person, PersonVo> {
@Override
@Mapping ( source = "age" , target = "age1" )
PersonVo sourceToTarget ( Person source) ;
}
日期类型:
增加一个生日,此生日,Person中存储为String,PersonVo中为Date,转换方式,如果存在多个字段需要自定义转换,使用@Mappings来定义多个转换方式,否则使用@Mapping即可
@ToString
@Data
public class PersonVo {
private String name;
private Integer age1;
private Date birthday;
}
@Data
@ToString
@Accessors ( chain = true )
public class Person {
private String name;
private Integer age;
private String birthday;
}
@Mapper
public interface PersonMapper extends BaseMapping < Person, PersonVo> {
@Override
@Mappings ( {
@Mapping ( source = "age" , target = "age1" ) ,
@Mapping ( source = "birthday" , target = "birthday" , dateFormat = "yyyy-MM-dd HH:mm:ss" )
} )
PersonVo sourceToTarget ( Person source) ;
}
源字段经过处理之后,映射为目标中的字段:
PersonVo中增加一个ageIsRtTen的boolean类型,判断Person中的age 字段,如果大于10返回true,否则false
@Data
@ToString
@Accessors ( chain = true )
public class Person {
private String name;
private Integer age;
}
@ToString
@Data
public class PersonVo {
private String name;
private Integer age;
private Boolean ageIsRtTen;
}
@Mapper
public interface PersonMapper extends BaseMapping < Person, PersonVo> {
@Override
@Mapping ( target = "ageIsRtTen" , expression = "java(org.expression.AgeUtil.isRtTen(source.getAge()))" )
PersonVo sourceToTarget ( Person source) ;
}
public class AgeUtil {
public static Boolean isRtTen ( Integer age) {
return age!= null && age> 10 ;
}
}
public class PersonTest {
public static void main ( String[ ] args) {
PersonMapper mapper = Mappers. getMapper ( PersonMapper. class ) ;
Person person = new Person ( ) . setAge ( 11 ) . setName ( "lisi" ) ;
PersonVo personVo = mapper. sourceToTarget ( person) ;
System. out. println ( personVo) ;
Person person1 = new Person ( ) . setAge ( 1 ) . setName ( "lisi" ) ;
PersonVo personVo1 = mapper. sourceToTarget ( person1) ;
System. out. println ( personVo1) ;
}
}
PersonVo ( name= lisi, age= 11 , ageIsRtTen= true )
PersonVo ( name= lisi, age= 1 , ageIsRtTen= false )
源字段为字符串,目标字段为枚举类型
字符串到枚举的映射
只能使用expression方法来进行映射 代码@Data
@ToString
@Accessors ( chain = true )
public class Person {
private String name;
private Integer age;
private String sex;
}
@ToString
@Data
public class PersonVo {
private String name;
private Integer age;
private SexEnum sexEnum;
}
@Mapper
public interface PersonMapper extends BaseMapping < Person, PersonVo> {
@Override
@Mapping ( target = "sexEnum" , expression = "java(org.constant.SexEnum.convertSexNum(source.getSex()))" )
PersonVo sourceToTarget ( Person source) ;
}
public enum SexEnum {
MAN,
WOMAN;
public static SexEnum convertSexNum ( String sex) {
return Arrays. stream ( SexEnum. values ( ) ) . filter ( s- > s. name ( ) . equalsIgnoreCase ( sex) )
. findFirst ( ) . get ( ) ;
}
}
public class PersonTest {
public static void main ( String[ ] args) {
PersonMapper mapper = Mappers. getMapper ( PersonMapper. class ) ;
Person person = new Person ( ) . setAge ( 11 ) . setName ( "lisi" ) . setSex ( "man" ) ;
PersonVo personVo = mapper. sourceToTarget ( person) ;
System. out. println ( personVo) ;
}
}
PersonVo ( name= lisi, age= 11 , sexEnum= MAN)
枚举到枚举的映射
源目标是不同的枚举类 代码public enum SexEnum {
MAN,
WOMAN;
}
public enum CityEnum {
NANJING,
SHANGHAI;
}
@Data
@ToString
@Accessors ( chain = true )
public class Person {
private String name;
private Integer age;
private SexEnum sexEnum;
}
@ToString
@Data
public class PersonVo {
private String name;
private Integer age;
private CityEnum cityEnum;
}
@Mapper
public interface PersonMapper extends BaseMapping < Person, PersonVo> {
@Override
@Mapping ( target = "cityEnum" , source = "sexEnum" )
PersonVo sourceToTarget ( Person source) ;
@ValueMapping ( source = "MAN" , target = "SHANGHAI" )
@ValueMapping ( source = "WOMAN" , target = "NANJING" )
CityEnum sexToSexEnum ( SexEnum sexEnum) ;
}
public class PersonTest {
public static void main ( String[ ] args) {
PersonMapper mapper = Mappers. getMapper ( PersonMapper. class ) ;
Person person = new Person ( ) . setAge ( 11 ) . setName ( "lisi" ) . setSexEnum ( SexEnum. MAN) ;
PersonVo personVo = mapper. sourceToTarget ( person) ;
System. out. println ( personVo) ;
}
}
PersonVo ( name= lisi, age= 11 , cityEnum= SHANGHAI)
注意
枚举到枚举的映射,除了source到target映射外,还需要增加一个值得映射 @ValueMapping @ValueMapping必须是枚举到枚举的映射,不可以是字符串到枚举的映射,因为此处市将两个枚举的映射关系都进行了声明,而且不管是source还是target都必须是枚举中的常量,大小写也区分,否则报错 @ValueMapping相当于一个显示的映射表,必须是枚举到枚举,如果是通过此方法实现string到枚举的映射,则报错,可以看出提示表示,不可以从一个非枚举到枚举的映射 Error: ( 25 , 13 ) java: Can't generate mapping method from non- enum type to enum type.