Mapstruct的使用备忘【代替BeanUtils高效率属性拷贝】
文章目录
- Mapstruct的使用备忘【代替BeanUtils高效率属性拷贝】
- 1. 引入mapstruct依赖
- 2. 数据准备
- 2-1 准备一个子类,TestData
- 2-2 准备两个类,SourceData,TargetData,属性完全一样
- 3. 定义Mapper接口【注:这里的Mapper是mapstruct的,非mybatis的】
- 4. 使用Mapstruct实现属性拷贝
- 4-1 使用实例
- 4-2 使用了Mapstruct,程序运行后,编译的目录会自动生成Mapper的实现类【了解即可】
- 5 Mapstruct默认为浅拷贝,深拷贝的使用
- 5-1 再新建一个Mapper,实现深拷贝
- 5-2 编译目录下,自动生成Mapper的实现类如下,可以发现TestData 为new出来的
- 6 集合的拷贝
- 6-1 集合拷贝实例
- 7 类型不一致
- 7-1 类型不一致,拷贝机制如下
- 7-2 上述问题,若想要在编译时就提示出来
- 7-3 禁止隐式转换
- 8 忽略指定字段
Mapstruct的使用备忘【代替BeanUtils高效率属性拷贝】
1. 引入mapstruct依赖
<!--mapstruct核心--><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.6.0</version></dependency><!--mapstruct编译--><dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.6.0</version></dependency>
2. 数据准备
2-1 准备一个子类,TestData
public class TestData {private String id;public String getId() {return id;}public void setId(String id) {this.id = id;}
}
2-2 准备两个类,SourceData,TargetData,属性完全一样
public class SourceData {private String id;private String name;private TestData data;private Long createTime;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public TestData getData() {return data;}public void setData(TestData data) {this.data = data;}public Long getCreateTime() {return createTime;}public void setCreateTime(Long createTime) {this.createTime = createTime;}
}
package cn.mediinfo.entity;public class TargetData {private String id;private String name;private TestData data;private Long createTime;public String getId() {return id;}public void setId(String id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public TestData getData() {return data;}public void setData(TestData data) {this.data = data;}public Long getCreateTime() {return createTime;}public void setCreateTime(Long createTime) {this.createTime = createTime;}
}
3. 定义Mapper接口【注:这里的Mapper是mapstruct的,非mybatis的】
@Mapper
public interface BeanMapper { BeanMapper INSTANCE = Mappers.getMapper(BeanMapper.class);TargetData map(SourceData source);
}
4. 使用Mapstruct实现属性拷贝
4-1 使用实例
// 1. 准备一个SourceData类,设置好数据
SourceData source = new SourceData()
source.setId("123")
source.setName("abc")
source.setCreateTime(System.currentTimeMillis())
TestData testData = new TestData()
testData.setId("123")// 2. 使用Mapstruct实现属性拷贝
TargetData target = BeanMapper.INSTANCE.map(source)// 3. 打印出目标类的属性值
System.out.println(target.getId() + ":" + target.getName() + ":" + target.getCreateTime())
// 这里可以看到Source和Target中的TestData属性是一样,即mapstruct默认是浅拷贝
System.out.println(source.getData() == target.getData()) // 结果为: true
4-2 使用了Mapstruct,程序运行后,编译的目录会自动生成Mapper的实现类【了解即可】
// 上述实例运行后,项目根目录下会出现编译后的文件,
// target目录下打开BeanMapper(自己写的Mapper接口)路径,会看到编译后自动产生了实现类
public class BeanMapperImpl implements BeanMapper {public BeanMapperImpl() {}public TargetData map(SourceData source) {if (source == null) {return null;} else {TargetData targetData = new TargetData();targetData.setId(source.getId());targetData.setName(source.getName());targetData.setData(source.getData());targetData.setCreateTime(source.getCreateTime());return targetData;}}
}
5 Mapstruct默认为浅拷贝,深拷贝的使用
- 查看上方编译后的源码,可以验证mapstruct默认是浅拷贝,所以上方实例打印结果为true
- 若想实现深拷贝,在方法上添加注解@Mapping(mappingControl = DeepClone.class)即可
5-1 再新建一个Mapper,实现深拷贝
@Mapper
public interface BeanMapper2 {BeanMapper2 INSTANCE = Mappers.getMapper(BeanMapper2.class);// 使用@Mapping注解,加上mappingControl = DeepClone.class标识即可实现深拷贝@Mapping(source = "data", target = "data", mappingControl = DeepClone.class)TargetData map(SourceData source);
}
// 测试// 1. 准备一个SourceData类,设置好数据SourceData source = new SourceData();source.setId("123345");source.setName("abcderf");TestData testData1 = new TestData();testData1.setId("TTTTT");source.setData(testData1);source.setCreateTime(System.currentTimeMillis() );// 2. 使用Mapstruct实现属性拷贝 【这里BeanMapper2即实现了深拷贝】TargetData target = BeanMapper2.INSTANCE.map(source);System.out.println(target.getId() + ":" + target.getName() + ":" + target.getCreateTime());// 输出:false, 说明对象的子类为不通的实例,即实现了深拷贝System.out.println(source.getData() == target.getData());
5-2 编译目录下,自动生成Mapper的实现类如下,可以发现TestData 为new出来的
public class BeanMapper2Impl implements BeanMapper2 {public BeanMapper2Impl() {}public TargetData map(SourceData source) {if (source == null) {return null;} else {TargetData targetData = new TargetData();targetData.setData(this.testDataToTestData(source.getData()));targetData.setId(source.getId());targetData.setName(source.getName());targetData.setCreateTime(source.getCreateTime());return targetData;}}protected TestData testDataToTestData(TestData testData) {if (testData == null) {return null;} else {TestData testData1 = new TestData();testData1.setId(testData.getId());return testData1;}}
}
6 集合的拷贝
- 在Mapper接口中,添加一个如下的方法即可;
List<TestData> map(List<TestData> source);
6-1 集合拷贝实例
// 实例
// 1. Mapper中声明集合拷贝方法即可
@Mapper
public interface BeanMapper3 {BeanMapper3 INSTANCE = Mappers.getMapper(BeanMapper3.class);List<TestData> map(List<TestData> source);
}// 2. 测试实例
public class TestMapstructDemo{public static void main(String[] args) {TestData t1 = new TestData("1");TestData t2 = new TestData("3");TestData t3 = new TestData("4");List<TestData> sourceList = List.of(t1, t2, t3);// 实现集合的拷贝List<TestData> targetList = BeanMapper3.INSTANCE.map(sourceList);// 打印targetList,便可以看到各个元素的值了System.out.println( targetList );// 这里输出: true 说明集合中的每个TestData对象实例, 和sourceList中是一样的,即浅拷贝System.out.println( sourceList.get(0) == targetList.get(0) );}
}
7 类型不一致
7-1 类型不一致,拷贝机制如下
- 假设将TargetData的createTime改成int类型,编译后,生成代码如下:
7-2 上述问题,若想要在编译时就提示出来
可以看到它会默认帮我们转换,这是个隐藏的问题!!!
在Mapper注解上指定一些类型转换的策略,如:@Mapper(typeConversionPolicy = ReportingPolicy.ERROR)
@Mapper(typeConversionPolicy = ReportingPolicy.ERROR)
这样编译即会提示错误
java: Can
7-3 禁止隐式转换
如果将TargetData的createTime类型再改成string呢,编译又正常了,生成代码如下:
对于string和其它基础类型的包装类,它会隐式帮我们转换,这也是个隐藏问题,
如果希望在编译时就提示,可以自定义一个注解,并在Mapper中指定它,如下:
// 自定义注解
@Retention(RetentionPolicy.CLASS)
@MappingControl(MappingControl.Use.DIRECT)
@MappingControl(MappingControl.Use.MAPPING_METHOD)
@MappingControl(MappingControl.Use.COMPLEX_MAPPING)
public @interface ConversationMapping {
}// 使用 ==> mappingControl = ConversationMapping.class
@Mapper(typeConversionPolicy = ReportingPolicy.ERROR, mappingControl = ConversationMapping.class)
重新编译会提示报错!!!
java: Can't map property "Long createTime" to "String createTime". Consider to declare/implement a mapping method: "String map(Long value)".
8 忽略指定字段
// 使用Mapping注解的ignore属性
@Mapping(target = "id", ignore = true)
// 如果想忽略某些字段,并且复用起来,可以定义一个IgnoreFixedField注解,然后打在方法上
@Mapping(target = "id", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "updateTime", ignore = true)
@Target(METHOD)
@Retention(RUNTIME)
@Documented
@interface IgnoreFixedField {
}@IgnoreFixedField // 加上这个自定义注解后,就自动忽略id,createTime,updateTime几个属性了
@Mapping(target = "data", mappingControl = DeepClone.class)
TargetData map(SourceData source);