一、基础转换
1.1、基础类型
基本类型、包装类、BigDecimal转String默认使用DecimalFormat格式化,@Mapping#numberFormat可以指定格式,Date转String默认使用SimpleDateFormat格式化,如默认格式不符要求,可以用,@Mapping可以指定格式。
- enum和String:可直接转枚举定义的值,不支持MappingConstants.ANY_REMAINING
- BigDecimal(等)、基本数据类(包括包装类)和String:
- java.time.LocalDateTime(等)和String:
- java.util.Date转String:Date->String,
- java.sql.Timestamp和java.util.Date:
注意在@Mapping#target、source设置的属性的含义。mapstruct并不是按照设置的属性找到字段,在找此字段的getter/setter方法的,而是直接通过get/set前缀 + 属性值找到相应的方法。所以如果属性名与getter和setter方法名不对象,则会报错。
@Data
public class TransformBO {
private String level;
private int price;
private String date;
private Date timestamp;
private String localDateTime
}
@Data
public class TransformPO {
private LevelEnum level;
private BigDecimal price;
private Date date;
private Timestamp timestamp;
private LocalDateTime localDateTime;
}
@Mapper
public interface TestMapper {
TransformBO toTransformBO(TransformPO transformPO);
TransformPO toTransformPO(TransformBO transformPO);
}
//@Mapping(target = "date", dateFormat = "yyyy-MM-dd HH:mm")
//@Mapping(target = "price", numberFormat = "$#.00")
TransformBO transformBO = new TransformBO();
transformBO.setLevel("PERFECT");
transformBO.setPrice(12);
transformBO.setDate("2022-01-31 01:30:19");
transformBO.setTimestamp(new Date(System.currentTimeMillis()));
transformBO.setLocalDateTime("2022-01-31T01:34:47.986");
System.out.println(mapper.toTransformPO(transformBO));
-->互转后Date的出入值是比较大的,需要特殊处理
TransformBO(level=PERFECT, price=12, date=2022-01-31 01:35:21, timestamp=2022-01-31 01:35:21.024, localDateTime=2022-01-31T01:35:21.107)
TransformPO(level=PERFECT, price=12, date=Mon Jan 31 01:30:19 CST 2022, timestamp=2022-01-31 01:35:21.138, localDateTime=2022-01-31T01:34:47.986)
1.2、Booble
private Boolean delete;
private String delete;
transformPO.setDelete(false);
//正常输出
二、集合转换
2.1、List
这个比较简单,需要定义一个集合类和一个单体转换类,类的名字随意,但建议起成对的名字,便于记录。
@Mapper
public interface TestMapper {
TestBO testToBO(TestPO testPO);
List<TestBO> testToBOS(List<TestPO> testPOS);
}
2.2、Map
@Mapper
public interface TestMapper {
TestBO testToBO(TestPO testPO);
Map<TestBO, TestBO> testToBOS(Map<TestPO, TestPO> testPOS);
}
2.3、Enum
枚举通常是直接映射到同名的枚举对象中,不同名需@ValueMapping指定,并且源和目标上可以设置
- MappingConstants.ANY_REMAINING:只能用在source上,标识source中除了同名自动映射和指定映射外,其余所有对象都映射到指定的target对象上;
- MappingConstants.ANY_UNMAPPED:只能用在source上,不能和MappingConstants.ANY_REMAINING同时使用,会将source除指定对象(不包括同名自动映射)的剩余对象映射到target对象,只是为了省事;
- MappingConstants.NULL:设置在source上表示source是null时,会被映射到指定的target;设置在target上表示source对象映射到null。
enum LevelEnum {
PERFECT(1, "完美"),
PASS(2, "合格"),
normal_status(3, "普通"),
failed(4, "不及格"),
normal(5, "正常");
}
enum DisableStatus {
able_status(1, "完美"),
disable_status(2, "合格"),
normal_status(3, "普通"),
failed_status(4, "不及格"),
ok_status(5, "还行"),
fine_status(6, "可以");
}
@Mapper
interface TestMapper {
@ValueMappings({
@ValueMapping(source = "able_status", target = "PERFECT"),//一对一映射
@ValueMapping(source = MappingConstants.NULL, target = "PASS"),//
@ValueMapping(source = "failed_status", target = MappingConstants.NULL),//==在新代码中failed_status=null
@ValueMapping(source = MappingConstants.ANY_REMAINING, target = "normal"),//==default=normal
})
LevelEnum toEnum(DisableStatus disableStatus);
}
public class TestMapperImpl implements TestMapper {
@Override
public LevelEnum toEnum(DisableStatus disableStatus) {
if ( disableStatus == null ) {
return LevelEnum.PASS;//--------
}
LevelEnum levelEnum;
switch ( disableStatus ) {
case able_status: levelEnum = LevelEnum.PERFECT;
break;
case failed_status: levelEnum = null;
break;
case normal_status: levelEnum = LevelEnum.normal_status;
break;
default: levelEnum = LevelEnum.normal;
}
return levelEnum;
}
}
//生成的代码
@Mapper
public interface TestMapper {
@ValueMappings({
@ValueMapping(source = MappingConstants.ANY_UNMAPPED, target = "normal_status"),
})
LevelEnum toEnum(DisableStatus disableStatus);
}
@Component
public class TestMapperImpl implements TestMapper {
@Override
public LevelEnum toEnum(DisableStatus disableStatus) {
if ( disableStatus == null ) {
return null;
}
LevelEnum levelEnum;
switch ( disableStatus ) {
default: levelEnum = LevelEnum.normal_status;
}
return levelEnum;
}
}
2.3.1、自定义名称转换
可以通过删除或添加源枚举字符串的前后缀来映射目标枚举对象,这也是一种快捷方式。@EnumMapping#nameTransformationStrategy支持的参数有:suffix(添加源后缀)、stripSuffix(删除源后缀)、prefix(添加源前缀)、stripPrefix(删除源前缀)。
@Mapper
public interface TestMapper {
@EnumMapping(nameTransformationStrategy = "stripSuffix", configuration = "_status")
LevelEnum toEnum(DisableStatus disableStatus);
}
//比如原Enum值为
public enum LevelEnum {
able_status(1, "完美")
}
//生成的源码为
@Override
public LevelEnum toEnum(DisableStatus disableStatus) {
if ( disableStatus == null ) {
return null;
}
LevelEnum levelEnum;
switch ( disableStatus ) {
case able_status: levelEnum = LevelEnum.able;
break;
case disable_status: levelEnum = LevelEnum.disable;
break;
case normal_status: levelEnum = LevelEnum.normal;
break;
case failed_status: levelEnum = LevelEnum.failed;
break;
case ok_status: levelEnum = LevelEnum.ok;
break;
case fine_status: levelEnum = LevelEnum.fine;
break;
default: throw new IllegalArgumentException( "Unexpected enum constant: " + disableStatus );
}
return levelEnum;
}
三、数据映射策略
3.1、@mappingControl属性映射优先级策略(不建议修改)
在mapStruct框架中可以从【全局-类-属性】等地方可以配置映射策略,@MappingConfig,@Mapper,@BeanMapping,@Mapping越往后的优先级越高,这4个注解都有mappingControl属性,默认值是MappingControl.class,可选值还有NoComplexMapping.class、DeepClone.class,用于控制每个字段属性映射的方式;
3.1.1、MappingControl.class的优先级逻辑如下:
- MappingControl.Use.DIRECT——直接复制具有相同属性类型的字段,但如果有自定义的映射配置@Mapping和导入了其他Mapper的自定义的映射,会将直接赋值覆盖;自定义的映射配置@Mapping优先级高于导入了其他Mapper的自定义的映射;
- MappingControl.Use.MAPPING_METHOD——属性类型不同的字段,会去查找将源属性的类型作为参数,将目标属性的类型作为返回的映射方法(包括自定义方法、抽象类实现方法和接口默认方法);
- MappingControl.Use.BUILT_IN_CONVERSION——如果不存在上述方法,查找内置转换方法,就像BigDecimal转String;
- MappingControl.Use.COMPLEX_MAPPING——如果内置转换方法不存在,将进行一些复杂转换,也就是结合现有的映射方法和内置转换,互相嵌套生成一个能够映射的方法;
- 如果都没有,则会自动生成子映射方法,就像上面的例子若是不指定TestPO到TestBO的接口方法,则也会主动生成;
- 若这也没法生成则编译就会报错了。
3.1.2、NoComplexMapping.class的优先级逻辑如下:
- MappingControl.Use.DIRECT——直接复制具有相同属性类型的字段,但如果有自定义的映射配置@Mapping和导入了其他Mapper的自定义的映射,会将直接赋值覆盖;自定义的映射配置@Mapping优先级高于导入了其他Mapper的自定义的映射;
- MappingControl.Use.MAPPING_METHOD——属性类型不同的字段,会去查找将源属性的类型作为参数,将目标属性的类型作为返回的映射方法(包括自定义方法、抽象类实现方法和接口默认方法);
- MappingControl.Use.BUILT_IN_CONVERSION——如果不存在上述方法,查找内置转换方法,就像BigDecimal转String;
- 如果都没有,则会自动生成子映射方法,就像上面的例子若是不指定TestPO到TestBO的接口方法,则也会主动生成;
- 若这也没法生成则编译就会报错了。
3.1.3、DeepClone.class的优先级逻辑如下:
- MappingControl.Use.MAPPING_METHOD——属性类型不同的字段,会去查找将源属性的类型作为参数,将目标属性的类型作为返回的映射方法(包括自定义方法、抽象类实现方法和接口默认方法)。
//比如使用DeepClone的mappingControl,这样我们就只能进行MappingControl.Use.MAPPING_METHOD映射了
@Mapper(mappingControl = DeepClone.class)
public interface TestMapper {
@Mapping(target = "localDateTime", dateFormat = "yyyy-MM-dd HH")
TransformBO toTransformBO(TransformPO transformPO);
}
3.2、@TargetType泛型的应用-将映射目标类型传递给自定义映射器
就是在使用@Mapper#uses()时,使用的自定义映射器中的方法可以接受Class<T>对象,要使用 @TargetType标注。可用于一些通用设置。
@Data
public class BasePO {
private Long id;
}
@Data
public class TestFivePO {
private TestFourPO test;
}
@EqualsAndHashCode(callSuper = true)
@Data
public class TestFourPO extends BasePO {
}
//-----
@Data
public class BaseBO {
private Long id;
private String name;
}
@Data
public class TestSevenBO {
private TestSixBO test;
}
@EqualsAndHashCode(callSuper = true)
@Data
public class TestSixBO extends BaseBO {
}
------------源码实现--------------------------------
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {
TestSevenBO testToBO(TestFivePO testPO);
}
@Mapper
public class BaseMapper {
public <T extends BaseBO> T testToBO(BasePO basePO, @TargetType Class<T> baseBOClass) {
try {
T t = baseBOClass.newInstance();
t.setId(basePO.getId());
return t;
} catch (Exception e) {
return null;
}
}
}
------------生成的源码实现--------------------------------
@Component
public class TestMapperImpl implements TestMapper {
@Autowired
private BaseMapper baseMapper;
public TestMapperImpl() {
}
public TestSevenBO testToBO(TestFivePO testPO) {
if (testPO == null) {
return null;
} else {
TestSevenBO testSevenBO = new TestSevenBO();
testSevenBO.setTest((TestSixBO)this.baseMapper.testToBO(testPO.getTest(), TestSixBO.class));
return testSevenBO;
}
}
}
3.3、@Context将上下文ThreadLocal或状态对象传递给自定义方法
就是线程间共享值的一种方式,用了ThreadLocal特性。
@Data
public class ThreadLocalContext {
private ThreadLocal<Object> threadLocal;
public ThreadLocalContext() {
threadLocal = new ThreadLocal<>();
}
}
@Mapper
public class BaseMapper {
public TestSixBO toTestSixBOWithContext(TestFourPO testFourPO, @Context ThreadLocalContext threadLocalContext) {
TestSixBO testBO = new TestSixBO();
testBO.setId(testFourPO.getId());
testBO.setName((String) threadLocalContext.getThreadLocal().get());
return testBO;
}
}
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {
TestSevenBO testToBO(TestFivePO testPO, @Context ThreadLocalContext threadLocalContext);
}
@Component
public class TestMapperImpl implements TestMapper {
@Autowired
private BaseMapper baseMapper;
@Override
public TestSevenBO testToBO(TestFivePO testPO, ThreadLocalContext threadLocalContext) {
if ( testPO == null ) {
return null;
}
TestSevenBO testSevenBO = new TestSevenBO();
testSevenBO.setTest( baseMapper.toTestSixBOWithContext( testPO.getTest(), threadLocalContext ) );
return testSevenBO;
}
}
ThreadLocalContext threadLocalContext = new ThreadLocalContext();
threadLocalContext.getThreadLocal().set("xx");
TestFivePO testFivePO = new TestFivePO();
TestFourPO testFourPO = new TestFourPO();
testFourPO.setId(1L);
testFivePO.setTest(testFourPO);
System.out.println(testMapper.testToBO(testFivePO, threadLocalContext));
---
TestSevenBO(test=TestSixBO(super=BaseBO(id=1, name=xx)))
3.4、@Qualifier区别相同参数类型和返回类型的映射方法
@Mapper
public class BaseMapper {
public String toString1(String name) {
return name + "1";
}
public String toString2(String name) {
return name + "2";
}
}
toString1、toString2方法的参数类型和返回类型相同,编译直接报错
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {
TestSixBO testToBO(TestFourPO testPO);
}
3.4.1、解决方案1-自定义注解ElementType.METHOD
@Qualifier
@Target(ElementType.METHOD) //ElementType.TYPE
@Retention(RetentionPolicy.CLASS)
public @interface StringToString1 {
}
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface StringToString2 {
}
------------------------------
@Mapper(uses = {BaseMapper.class})
public interface TestMapper {
@Mapping(target = "name", qualifiedBy = {StringToString1.class})//qualifiedBy属性
TestSixBO testToBO(TestFourPO testPO);
}
@Mapper
public class BaseMapper {
@StringToString1
public String toString1(String name) {
return name + "1";
}
@StringToString2
public String toString2(String name) {
return name + "2";
}
}
------------------------生成的代码如下------------------------
@Component
public class TestMapperImpl implements TestMapper {
@Autowired
private BaseMapper baseMapper;
@Override
public TestSixBO testToBO(TestFourPO testPO) {
if ( testPO == null ) {
return null;
}
TestSixBO testSixBO = new TestSixBO();
// 使用qualifiedBy中设置的注解的方法
testSixBO.setName( baseMapper.toString1( testPO.getName() ) );
testSixBO.setId( testPO.getId() );
return testSixBO;
}
}
3.4.2、解决方案2-自定义注解ElementType.TYPE
@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface StringToString1 {
}
@Qualifier
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface StringToString2 {
}
@Mapper
@StringToString1//----------------------
public class BaseMapper {
public String toString1(String name) {
return name + "1";
}
}
@Mapper
@StringToString2
public class BaseMapper2 {
public String toString2(String name) {
return name + "2";
}
}
@Mapper(uses = {BaseMapper.class, BaseMapper2.class})
public interface TestMapper {
@Mapping(target = "name", qualifiedBy = {StringToString2.class})
TestSixBO testToBO(TestFourPO testPO);
}
3.4.3、解决方案3-@Named取代定义一个@Qualifier的接口
//若类上注解了@Named,mapper中引入了此类,要想使用引入类中自定义的映射方法,映射字段时就必须指定qualifiedByName
@Mapper
@Named("StringToString1")
public class BaseMapper {
public String toString1(String name) {
return name + "1";
}
}
@Mapper
@Named("StringToString2")
public class BaseMapper2 {
public String toString2(String name) {
return name + "2";
}
}
@Mapper(uses = {BaseMapper.class, BaseMapper2.class})
public interface TestMapper {
@Mapping(target = "name", qualifiedByName = {"StringToString2"})//qualifiedByName属性
TestSixBO testToBO(TestFourPO testPO);
}
四、集合策略
通过@Mapping#collectionMappingStrategy设置集合的映射策略:CollectionMappingStrategy.ACCESSOR_ONLY(默认)、CollectionMappingStrategy.SETTER_PREFERRED、CollectionMappingStrategy.ADDER_PREFERRED、CollectionMappingStrategy.TARGET_IMMUTABLE。
public class TestPOS {
private List<TestPO> list;
public List<TestPO> getList() {
return list;
}
public void addList(TestPO testPO) {
this.list.add(testPO);
}
}
public class TestBOS {
private List<TestBO> list;
public List<TestBO> getList() {
return list;
}
public void addList(TestBO testBO) {
this.list.add(testBO);
}
}
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
public interface TestMapper {
TestBO toBO(TestPO testPO);
TestBOS toBOS(TestPOS testPOS);
}
4.1、使用默认集合策略CollectionMappingStrategy.ACCESSOR_ONLY
@Data
public class TestPOS {
private List<TestPO> list;
}
@Data
public class TestBOS {
private List<TestBO> list;
}
@Mapper
public interface TestMapper {
TestBO toBO(TestPO testPO);
void toBOS(TestPOS testPOS, @MappingTarget TestBOS testBOS);
}
testBOS.getList().clear();
testBOS.getList().addAll( list )//先清空再添加
4.2、使用CollectionMappingStrategy.ADDER_PREFERRED策略
手动创建add方法,使用CollectionMappingStrategy.ADDER_PREFERRED
这里在介绍下List属性字段名的映射规则,
public class TestBOS {
private List<TestBO> oneTest_;
public List<TestBO> getOneTestList() {
return oneTest_;
}
public void addOneTest(TestBO testBO) {//手动创建了此方法
oneTest_.add(testBO);
}
}
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
public interface TestMapper {
@Mapping(target = "oneTest_", source = "tests") //这处的oneTest_要改成oneTestList才能正确调用addOneTest方法
TestBOS toBean(TestPOS testPO);
}
自定义add方法,使用CollectionMappingStrategy.ADDER_PREFERRED
public class TestBOS {
private List<Integer> oneTest_;
public List<TestBO> getOneTestList() {
return new ArrayList<>();
}
public void addOneTest(Integer integer) {//入参不一样
oneTest_.add(integer);
}
}
//testBOS.getOneTestList().addAll( list )
public class TestBOS {
private List<Integer> oneTest_;
public List<TestBO> getOneTestList() {
return new ArrayList<>();
}
public void addOneTest(TestBO testBO) {//入参不一样
oneTest_.add(testBO.getId().intValue());
}
}
//testBOS.addOneTest( testPOToTestBO( test ) )
只有add方法,使用CollectionMappingStrategy.ADDER_PREFERRED策略
TestBOS testBOS = new TestBOS();
if ( testPOS.getList() != null ) {
for ( TestPO list : testPOS.getList() ) {
testBOS.addList( toBO( list ) );
}
}
去掉add方法,使用CollectionMappingStrategy.ACCESSOR_ONLY策略
TestBOS testBOS = new TestBOS();
if ( testBOS.getList() != null ) {
List<TestBO> list = testPOListToTestBOList( testPOS.getList() );
if ( list != null ) {
testBOS.getList().addAll( list );
}
}
//以上结论是一样的,都是保持原集合内容不变,再增加新内容。
4.3、使用CollectionMappingStrategy.TARGET_IMMUTABLE策略
@Data
public class TestPOS {
private List<TestPO> list;
}
@Data
public class TestBOS {
private List<TestBO> list;
}
@Mapper(collectionMappingStrategy = CollectionMappingStrategy.TARGET_IMMUTABLE)
public interface TestMapper {
TestBO toBO(TestPO testPO);
void toBOS(TestPOS testPOS, @MappingTarget TestBOS testBOS);
}
@Override
public void toBOS(TestPOS testPOS, TestBOS testBOS) {
if ( testPOS == null ) {
return;
}
testBOS.setList( testPOListToTestBOList( testPOS.getList() ) );
}
//这个直接使用setter方式,会覆盖掉原集合的内容