文章目录
- 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 忽略指定字段
- 9. 综合案例一 【自定义转换器的使用 @Named()+qualifiedByName = {} 】
- 9-1 准备如下几个Class
- 9-1-1 UserDao
- 9-1-2 UserDao
- 9-1-3 SexEnum
- 9-2 Mapper
- 9-2-1 自定义的UserMapper
- 9-2-2 上述Mapper用到了两个自定义的转换器
- 9-3 使用测试
- 9-4 编译后Mapper接口的实现类代码【了解即可】
- 10. 综合案例二【自定义转换器的使用 @Named()+qualifiedByName = "" 】
- 10-1 准备如下几个Class
- 10-1-1 People1
- 10-1-2 People2
- 10-1-3 Address
- 10-1-4 SexEnum
- 10-2 Mapper
- 10-2-1 自定义的PeopleMapper
- 10-2-2 上述Mapper用到的自定义转换器
- 10-3 使用测试
- 10-4 编译后Mapper接口的实现类代码【了解即可】
- 11. 综合案例三【参数赋值、更新对象属性、映射反转、通过Spring依赖注入使用等】
- 11-1 准备如下几个Class
- 11-2 Mapper及测试示例
- 11-2-1 自定义Mapper1 ⇒ 多参数赋值
- 11-2-2 直接使用参数作为属性值
- 11-2-3 更新对象属性【@Mapping+@MappingTarget】
- 11-2-4 映射反转【@InheritInverseConfiguration和@InheritConfiguration】
- 11-2-4-1 示例准备
- 11-2-4-2 Mapper【@InheritInverseConfiguration】
- 11-2-4-2 Mapper【@InheritConfiguration】 使用已有的映射更新对象属性
- 11-2-5 使用Spring依赖注入
- 12 MapStruct之@BeforeMapping和@AfterMapping注解的用法
- 12-1 注解的作用
- 12-2 注解的使用【Mapper接口中使用、通过@Mapper注解的uses属性指定使用、通过@Context参数的类型中实现】
- 12-2-1 示例准备
- 12-2-2 Mapper
- 12-2-3 测试
- 12-2-4 分析
- 12-2-5 通过@Mapper注解的uses属性,指定的类型(类或接口)中声明@BeforeMapping和@AfterMapping ,如下:
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);
9. 综合案例一 【自定义转换器的使用 @Named()+qualifiedByName = {} 】
9-1 准备如下几个Class
9-1-1 UserDao
import lombok.Data;
import java.sql.Timestamp;
@Data
public class UserDAO {
// 主键
private Long id;
// 姓名
private String name;
// 性别
private Integer sex;
// 描述
private String remark;
// 创建时间
private Timestamp createTime;
}
9-1-2 UserDao
import com.gougou.mapstruct.enums.SexEnum;
import lombok.Data;
import java.io.Serializable;
/**
* dto:网络传输对象
*/
@Data
public class UserDTO implements Serializable {
private static final long serialVersionUID = -2767215193284523251L;
// 主键
private String id;
// 姓名
private String name;
// 性别
private SexEnum sex;
// 描述
private String desc;
// 创建时间
private String createTime;
}
9-1-3 SexEnum
import lombok.Getter;
import lombok.Setter;
public enum SexEnum {
man(1, "男"),
woman(0, "女");
@Setter
@Getter
private Integer code;
@Setter
@Getter
private String name;
SexEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
public static SexEnum of(Integer code){
for(SexEnum sexEnum:SexEnum.values()){
if(sexEnum.code.equals(code)){
return sexEnum;
}
}
return null;
}
}
9-2 Mapper
9-2-1 自定义的UserMapper
import com.gougou.mapstruct.dao.UserDAO;
import com.gougou.mapstruct.dto.UserDTO;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* UserDTO与UserDAO之间的转化类
*/
@Mapper(uses = {
SexEnumIntegerMapper.class,
StringTimestampMapper.class
})
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mappings({
@Mapping(source = "sex", target = "sex",qualifiedByName = {"SexEnumIntegerMapper", "integerBySexEnum"}),
@Mapping(source = "desc", target = "remark"),
@Mapping(source = "createTime", target = "createTime",qualifiedByName = {"StringTimestampMapper", "timestampByString"})
})
UserDAO toDO(UserDTO userDTO);
List<UserDAO> toDOs(List<UserDTO> userDTOList);
@Mappings({
@Mapping(source = "sex", target = "sex",qualifiedByName = {"SexEnumIntegerMapper", "sexEnumByInteger"}),
@Mapping(source = "remark", target = "desc"),
@Mapping(source = "createTime", target = "createTime",qualifiedByName = {"StringTimestampMapper", "stringByTimestamp"})
})
UserDTO toDTO(UserDAO userDAO);
List<UserDTO> toDTOs(List<UserDAO> userDAOList);
}
9-2-2 上述Mapper用到了两个自定义的转换器
import com.gougou.mapstruct.enums.SexEnum;
import org.mapstruct.Named;
/**
* SexEnum与Integer之间的转化
*/
@Named("SexEnumIntegerMapper")
public class SexEnumIntegerMapper {
@Named("sexEnumByInteger")
public SexEnum sexEnumByInteger(Integer intParam){
return SexEnum.of(intParam);
}
@Named("integerBySexEnum")
public Integer integerBySexEnum(SexEnum sexEnum){
return sexEnum.getCode();
}
}
import org.mapstruct.Named;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
/**
* String与timestamp之间的转化
*/
@Named("StringTimestampMapper")
public class StringTimestampMapper {
private static final String dateFormatStr = "yyyy-MM-dd HH:mm:ss";
@Named("timestampByString")
public Timestamp timestampByString(String strParam) {
SimpleDateFormat sf = new SimpleDateFormat(dateFormatStr);
java.util.Date date = null;
try {
date = sf.parse(strParam);
} catch (ParseException e) {
e.printStackTrace();
}
return new java.sql.Timestamp(date.getTime());
}
@Named("stringByTimestamp")
public String stringByTimestamp(Timestamp timestamp) {
DateFormat df = new SimpleDateFormat(dateFormatStr);
return df.format(timestamp);
}
}
9-3 使用测试
import com.gougou.mapstruct.dao.UserDAO;
import com.gougou.mapstruct.dto.UserDTO;
import com.gougou.mapstruct.enums.SexEnum;
import com.gougou.mapstruct.transfer.UserMapper;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class MainTest {
private UserDTO userDTO = new UserDTO();
private List<UserDTO> userDTOList = new ArrayList<>(2);
private UserDAO userDAO = new UserDAO();
private List<UserDAO> userDAOList = new ArrayList<>(2);
@Before
public void initUserDTO() {
userDTO.setId("1122");
userDTO.setDesc("这是张三");
userDTO.setName("张三");
userDTO.setSex(SexEnum.man);
userDTO.setCreateTime("2020-05-06 19:00:00");
userDAO.setId(3377L);
userDAO.setRemark("这是李梅梅");
userDAO.setName("李梅梅");
userDAO.setSex(0);
userDAO.setCreateTime(new java.sql.Timestamp(1588765009399L));
UserDTO userDTO2 = new UserDTO();
userDTO2.setId("2211");
userDTO2.setDesc("这是张三2");
userDTO2.setName("张三2");
userDTO2.setSex(SexEnum.man);
userDTO2.setCreateTime("2020-05-06 19:49:00");
UserDAO userDAO2 = new UserDAO();
userDAO2.setId(7733L);
userDAO2.setRemark("这是李梅梅2");
userDAO2.setName("李梅梅2");
userDAO2.setSex(0);
userDAO2.setCreateTime(new java.sql.Timestamp(1588766094618L));
userDAOList.add(userDAO);
userDAOList.add(userDAO2);
userDTOList.add(userDTO);
userDTOList.add(userDTO2);
}
/**
* DAO -> DTO
*/
@Test
public void test1() {
UserDTO userDTO1 = UserMapper.INSTANCE.toDTO(userDAO);
// UserDTO(id=3377, name=李梅梅, sex=woman, desc=这是李梅梅, createTime=2020-05-06 19:36:49)
System.out.println(userDTO1.toString());
}
/**
* DTO -> DAO
*/
@Test
public void test2() {
UserDAO userDAO1 = UserMapper.INSTANCE.toDO(userDTO);
// UserDAO(id=1122, name=张三, sex=1, remark=这是张三, createTime=2020-05-06 19:00:00.0)
System.out.println(userDAO1.toString());
}
/**
* List<DAO> -> List<DTO>
*/
@Test
public void test3() {
List<UserDTO> userDTOList1 = UserMapper.INSTANCE.toDTOs(userDAOList);
/**
* UserDTO(id=3377, name=李梅梅, sex=woman, desc=这是李梅梅, createTime=2020-05-06 19:36:49)
* UserDTO(id=7733, name=李梅梅2, sex=woman, desc=这是李梅梅2, createTime=2020-05-06 19:54:54)
*/
userDTOList1.stream().forEach(x -> System.out.println(x));
}
/**
* List<DTO> -> List<DAO>
*/
@Test
public void test4() {
List<UserDAO> userDAOList1 = UserMapper.INSTANCE.toDOs(userDTOList);
/**
* UserDAO(id=1122, name=张三, sex=1, remark=这是张三, createTime=2020-05-06 19:00:00.0)——————这里的格式是TimeStamp的toString方法默认的实现,与本次转换无关
* UserDAO(id=2211, name=张三2, sex=1, remark=这是张三2, createTime=2020-05-06 19:49:00.0)
*/
userDAOList1.stream().forEach(x -> System.out.println(x));
userDAOList1.stream().forEach(x -> System.out.println(x.getCreateTime()));
}
}
9-4 编译后Mapper接口的实现类代码【了解即可】
import com.gougou.mapstruct.dao.UserDAO;
import com.gougou.mapstruct.dto.UserDTO;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-05-06T19:40:20+0800",
comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper {
private final SexEnumIntegerMapper sexEnumIntegerMapper = new SexEnumIntegerMapper();
private final StringTimestampMapper stringTimestampMapper = new StringTimestampMapper();
@Override
public UserDAO toDO(UserDTO userDTO) {
if ( userDTO == null ) {
return null;
}
UserDAO userDAO = new UserDAO();
userDAO.setRemark( userDTO.getDesc() );
userDAO.setCreateTime( stringTimestampMapper.timestampByString( userDTO.getCreateTime() ) );
userDAO.setSex( sexEnumIntegerMapper.integerBySexEnum( userDTO.getSex() ) );
if ( userDTO.getId() != null ) {
userDAO.setId( Long.parseLong( userDTO.getId() ) );
}
userDAO.setName( userDTO.getName() );
return userDAO;
}
@Override
public List<UserDAO> toDOs(List<UserDTO> userDTOList) {
if ( userDTOList == null ) {
return null;
}
List<UserDAO> list = new ArrayList<UserDAO>( userDTOList.size() );
for ( UserDTO userDTO : userDTOList ) {
list.add( toDO( userDTO ) );
}
return list;
}
@Override
public UserDTO toDTO(UserDAO userDAO) {
if ( userDAO == null ) {
return null;
}
UserDTO userDTO = new UserDTO();
userDTO.setCreateTime( stringTimestampMapper.stringByTimestamp( userDAO.getCreateTime() ) );
userDTO.setSex( sexEnumIntegerMapper.sexEnumByInteger( userDAO.getSex() ) );
userDTO.setDesc( userDAO.getRemark() );
if ( userDAO.getId() != null ) {
userDTO.setId( String.valueOf( userDAO.getId() ) );
}
userDTO.setName( userDAO.getName() );
return userDTO;
}
@Override
public List<UserDTO> toDTOs(List<UserDAO> userDAOList) {
if ( userDAOList == null ) {
return null;
}
List<UserDTO> list = new ArrayList<UserDTO>( userDAOList.size() );
for ( UserDAO userDAO : userDAOList ) {
list.add( toDTO( userDAO ) );
}
return list;
}
}
10. 综合案例二【自定义转换器的使用 @Named()+qualifiedByName = “” 】
10-1 准备如下几个Class
10-1-1 People1
@Data
@NoArgsConstructor
@AllArgsConstructor
public class People1 {
//主键
private String id;
//名称
private String name1;
//编号
private String no1;
//年龄
private String age1;
//身高(cm)
private Long height1;
//体重(kg)
private Integer weight1;
//性别
private SexEnum sex1;
//创建时间
private Date createTime1;
//血型
private Character bloodType1;
//工资卡
private Double salary1;
//小金库
private String caseDough1;
//地址
private Address address;
}
10-1-2 People2
@Data
@NoArgsConstructor
@AllArgsConstructor
public class People2 {
//主键
private String id;
//名称
private String name2;
//编号
private String no2;
//年龄
private Integer age2;
//身高(m)
private Double height2;
//体重(斤)
private String weight2;
//性别(1:男,0:女)
private Integer sex2;
//创建时间
private String createTime2;
//血型
private String bloodType2;
//工资卡
private String salary2;
//小金库
private Integer caseDough2;
//地址(所在省)
private String province;
}
10-1-3 Address
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Address {
//省
private String province;
//市
private String city;
//县
private String county;
//街道
private String street;
}
10-1-4 SexEnum
public enum SexEnum {
MAN(1, "男"),
WOMAN(0, "女");
private Integer code;
private String desc;
SexEnum(Integer code, String desc) {
this.code = code;
this.desc = desc;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
}
10-2 Mapper
10-2-1 自定义的PeopleMapper
@Mapper(uses={WeightMapper.class})
public interface PeopleMapper {
PeopleMapper INSTANCE = Mappers.getMapper(PeopleMapper.class);
@Mappings({
@Mapping(source = "name1", target = "name2"),
@Mapping(source = "no1", target = "no2"),
@Mapping(source = "age1", target = "age2", ignore = true),
@Mapping(source = "height1", target = "height2", qualifiedByName = "heightConvert"),
@Mapping(source = "weight1", target = "weight2", qualifiedByName = {"WeightMapper", "WeightConvert"}),
@Mapping(source = "sex1.code",target = "sex2")
})
People2 toPeople2(People1 people1);
@Named("heightConvert")
default Double heightConvert(Long height) {
return height * 1.0 / 100;
}
}
10-2-2 上述Mapper用到的自定义转换器
@Component
@Named("WeightMapper")
public class WeightMapper {
@Named("WeightConvert")
public String weightConvert(Integer weight){
//kg —— 斤
return (weight*2)+"斤";
}
}
10-3 使用测试
public class PeopleTest {
/**
* 输出结果:
* People1(id=0001, name1=张三, no1=001, age1=18, height1=183, weight1=70, sex1=MAN, createTime1=Thu Jul 22 10:19:36 CST 2010, bloodType1=A, salary1=28.09888888888, caseDough1=1.08888888888, address=Address(province=安徽省, city=安庆市, county=太湖县, street=123街道))
* People2(id=0001, name2=张三, no2=001, age2=null, height2=1.83, weight2=140斤, sex2=1, createTime2=2010-07-22 10:19:36:000, bloodType2=A型血, salary2=28.099, caseDough2=1, province=安徽省)
*/
@Test
public void test1() {
People2 people2 = PeopleMapper.INSTANCE.toPeople2(setPeople1());
System.out.println(people2);
}
private People1 setPeople1() {
People1 people1 = new People1();
people1.setId("0001");
people1.setName1("张三");
people1.setNo1("001");
people1.setAge1("18");
people1.setHeight1(183L);
people1.setWeight1(70);
people1.setSex1(SexEnum.MAN);
people1.setCreateTime1(new Date(1279765176000L));
people1.setBloodType1('A');
people1.setSalary1(28.09888888888);
people1.setCaseDough1("1.08888888888");
people1.setAddress(new Address("安徽省", "安庆市", "太湖县", "123街道"));
System.out.println(people1);
return people1;
}
}
10-4 编译后Mapper接口的实现类代码【了解即可】
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import javax.annotation.Generated;
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2020-07-23T11:04:13+0800",
comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_201 (Oracle Corporation)"
)
public class PeopleMapperImpl implements PeopleMapper {
private final WeightMapper weightMapper = new WeightMapper();
@Override
public People2 toPeople2(People1 people1) {
if ( people1 == null ) {
return null;
}
People2 people2 = new People2();
people2.setNo2( people1.getNo1() );
people2.setHeight2( heightConvert( people1.getHeight1() ) );
String province = people1AddressProvince( people1 );
if ( province != null ) {
people2.setProvince( province );
}
Integer code = people1Sex1Code( people1 );
if ( code != null ) {
people2.setSex2( code );
}
if ( people1.getSalary1() != null ) {
people2.setSalary2( new DecimalFormat( "0.000" ).format( people1.getSalary1() ) );
}
try {
if ( people1.getCaseDough1() != null ) {
people2.setCaseDough2( new DecimalFormat( "0." ).parse( people1.getCaseDough1() ).intValue() );
}
}
catch ( ParseException e ) {
throw new RuntimeException( e );
}
people2.setName2( people1.getName1() );
people2.setWeight2( weightMapper.weightConvert( people1.getWeight1() ) );
if ( people1.getCreateTime1() != null ) {
people2.setCreateTime2( new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss:SSS" ).format( people1.getCreateTime1() ) );
}
people2.setId( people1.getId() );
people2.setBloodType2( "A型血" );
return people2;
}
private String people1AddressProvince(People1 people1) {
if ( people1 == null ) {
return null;
}
Address address = people1.getAddress();
if ( address == null ) {
return null;
}
String province = address.getProvince();
if ( province == null ) {
return null;
}
return province;
}
private Integer people1Sex1Code(People1 people1) {
if ( people1 == null ) {
return null;
}
SexEnum sex1 = people1.getSex1();
if ( sex1 == null ) {
return null;
}
Integer code = sex1.getCode();
if ( code == null ) {
return null;
}
return code;
}
}
11. 综合案例三【参数赋值、更新对象属性、映射反转、通过Spring依赖注入使用等】
11-1 准备如下几个Class
@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;
}
11-2 Mapper及测试示例
11-2-1 自定义Mapper1 ⇒ 多参数赋值
@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);
}
@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);
}
11-2-2 直接使用参数作为属性值
@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);
}
@Test
public void test3(){
Student1 student1 = new Student1();
student1.setName1("王五");
Student2 student2 = StudentMapper.INSTANCE.toStudent2(student1,"天津市");
System.out.println(student2);
}
11-2-3 更新对象属性【@Mapping+@MappingTarget】
@Mapper
public abstract class StudentMapper {
public static final StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
// 更新对象属性主要通过@MappingTarget实现,方法无返回值
@Mapping(source = "name1",target = "name2")
abstract void updateStudent2(Student1 student1, @MappingTarget Student2 student2);
}
/**
* 转化前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);
}
11-2-4 映射反转【@InheritInverseConfiguration和@InheritConfiguration】
在实际的项目中,
经常需要将A ——> B,
还有就是将B——> A
根据我们目前所学到的mapStruct知识,我们是这样写的, 以下是一个示例。
写的很繁琐,很累赘!!!
实际上在people1转化为people2的时候,规则都配置了(下文称:原映射);
而people2转people的时候,这些规则又写了一遍(下文称:新映射),
很明显原映射与新映射是一对互逆的操作,
所以如果程序智能一点,在原映射存在的前提下,people2转people1的时候应该我就不用配置了,
或者基于原映射稍微修改就好了,这是最理想的状况。
那mapStruct能做到吗?能,当然能,不然mapStruct怎么能说强大呢,
使用@InheritInverseConfiguration。
它能满足我们的需求。该注解需要注意三点:
1)有一个注解@InheritConfiguration【该注解一般配合@MappingTarget实现更新对象属性时用】, 它与该注解长得很像,请注意区分,虽然他们的功能有点相似,但是他们是不同的注解;
2)该注解虽然可以实现反转赋值,但是有一种情况需要手动加强——原映射的Mapping中没有source(典型的使用了expression、constant属性的都属于)。对于有这种情况的属性,原映射与新映射都需要指定出来;
3)该注解只有一个属性name,它的值是原映射名。
你可以把它理解为原映射对应的方法的名字,即方法的名字就是映射名。
如上面的例子,规则名是"toPeople2"。那该注解可以这样写@InheritInverseConfiguration(name=“toPeople2”)。
11-2-4-1 示例准备
@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;
}
}
11-2-4-2 Mapper【@InheritInverseConfiguration】
@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注解,直接把上面映射关系匹配了,不用重新写一遍!
@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;
}
}
11-2-4-2 Mapper【@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()) );
}
}
11-2-5 使用Spring依赖注入
// 在自定义的Mapper上添加如下固定内容:
// @Mapper(componentModel = "spring")
// 注:上方注解内的名称固定为 "spring" !!!
@Mapper(componentModel = "spring")
public interface CarMapper {
CarDto carToCarDto(Car car);
}
//使用时注入即可
@Autowired
private CarMapper mapper;
12 MapStruct之@BeforeMapping和@AfterMapping注解的用法
12-1 注解的作用
作用
@BeforeMapping注解作用于方法上, 标记要在生成的映射方法开始时调用的方法。
@AfterMapping注解作用于方法之上,标记要在生成的映射方法的末尾调用的方法。
该两个注解指定的方法)既可以在抽象映射器类中实现,
也可以通过@Mapper注解的uses属性指定的类型(类或接口)声明,
还可以在用@Context参数的类型中实现,以便在映射方法中使用。
@BeforeMapping和@AfterMapping注解都没有任何属性!
12-2 注解的使用【Mapper接口中使用、通过@Mapper注解的uses属性指定使用、通过@Context参数的类型中实现】
12-2-1 示例准备
import lombok.Data;
import java.time.LocalDate;
@Data
public class Student {
private Long id;
private String name;
private String idCard;
private LocalDate birthday;
}
import lombok.Data;
import java.time.LocalDate;
@Data
public class StudentDTO {
private Long userId;
private String userName;
private String idNumber;
private LocalDate dateOfBirth;
}
12-2-2 Mapper
import com.ylz.mapstruct.domain.dto.StudentDTO;
import com.ylz.mapstruct.domain.entity.Student;
import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper(implementationName = "StudentConvertUtil",
implementationPackage = "com.ylz.mapstruct.domain.convert.impl")
public interface IStudentConvert {
IStudentConvert INSTANCE = Mappers.getMapper(IStudentConvert.class);
@Mapping(source = "id", target = "userId")
@Mapping(source = "name", target = "userName")
@Mapping(source = "name", target = "idNumber")
@Mapping(source = "birthday", target = "dateOfBirth")
StudentDTO student2StudentDTO(Student student);
@BeforeMapping
default void preProcess() {
System.out.println("执行前置处理......");
}
@AfterMapping
default void postProcess() {
System.out.println("执行后置处理......");
}
}
编译后的实现类代码
import com.ylz.mapstruct.domain.convert.IStudentConvert;
import com.ylz.mapstruct.domain.dto.StudentDTO;
import com.ylz.mapstruct.domain.entity.Student;
public class StudentConvertUtil implements IStudentConvert {
public StudentConvertUtil() {
}
public StudentDTO student2StudentDTO(Student student) {
this.preProcess();
if (student == null) {
return null;
} else {
StudentDTO studentDTO = new StudentDTO();
studentDTO.setUserId(student.getId());
studentDTO.setUserName(student.getName());
studentDTO.setIdNumber(student.getName());
studentDTO.setDateOfBirth(student.getBirthday());
this.postProcess();
return studentDTO;
}
}
}
12-2-3 测试
import java.time.LocalDate;
import com.ylz.mapstruct.domain.convert.impl.StudentConvertUtil;
import com.ylz.mapstruct.domain.dto.StudentDTO;
import com.ylz.mapstruct.domain.entity.Student;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class IStudentConvertTest {
@Test
void test() {
Student student = new Student();
student.setId(1111L);
student.setName("jack");
student.setIdCard("33333333333");
student.setBirthday(LocalDate.now());
System.out.println(StudentConvertUtil.INSTANCE.student2StudentDTO(student));
}
}
测试结果
12-2-4 分析
在IStudentConvert接口中,定义了三个方法,
分别是:
用@Mapping注解标注的映射方法student2StudentDTO,
用@BeforeMapping注解标注的前置处理方法preProcess,
用@AfterMapping注解标注的后置处理方法postProcess。
通过生成的实现类代码可以发现:
1、@BeforeMapping注解标注的方法会在映射方法中首先执行。
2、@AfterMapping注解标注的方法会在映射方法的最后一个返回语句之前执行。
3、还有一点需要注意:如果传入的参数是null,
会执行@BeforeMapping注解标注的方法,但不会执行@AfterMapping注解标注的方法!!!
4、可以同时用@BeforeMapping注解标注多个方法,
也可以同时用@AfterMapping注解标注多个方法。
在这种情况下,在生成的实现类中,这些方法会按照我们在接口或者抽象类中定义的顺序执行,如下图。
12-2-5 通过@Mapper注解的uses属性,指定的类型(类或接口)中声明@BeforeMapping和@AfterMapping ,如下:
import com.ylz.mapstruct.StudentFactory;
import com.ylz.mapstruct.domain.dto.StudentDTO;
import com.ylz.mapstruct.domain.entity.Student;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
@Mapper(implementationName = "StudentConvertUtil",
// 这里通过uses属性指定StudentFactory类,该类中声明了很多 @BeforeMapping和@AfterMapping方法
uses = StudentFactory.class,
implementationPackage = "com.ylz.mapstruct.domain.convert.impl")
public interface IStudentConvert {
IStudentConvert INSTANCE = Mappers.getMapper(IStudentConvert.class);
@Mapping(source = "id", target = "userId")
@Mapping(source = "name", target = "userName")
@Mapping(source = "name", target = "idNumber")
@Mapping(source = "birthday", target = "dateOfBirth")
StudentDTO student2StudentDTO(Student student);
}
StudentFactory
import org.mapstruct.AfterMapping;
import org.mapstruct.BeforeMapping;
public class StudentFactory {
@BeforeMapping
public static void preProcess() {
System.out.println("执行preProcess方法......");
}
@BeforeMapping
public static void preProcess2() {
System.out.println("执行preProcess2方法......");
}
@AfterMapping
public static void postProcess2() {
System.out.println("执行postProcess2方法......");
}
@AfterMapping
public static void postProcess() {
System.out.println("执行postProcess方法......");
}
}
IStudentConvert生成的实现类代码
import com.ylz.mapstruct.StudentFactory;
import com.ylz.mapstruct.domain.convert.IStudentConvert;
import com.ylz.mapstruct.domain.dto.StudentDTO;
import com.ylz.mapstruct.domain.entity.Student;
public class StudentConvertUtil implements IStudentConvert {
public StudentConvertUtil() {
}
public StudentDTO student2StudentDTO(Student student) {
StudentFactory.preProcess();
StudentFactory.preProcess2();
if (student == null) {
return null;
} else {
StudentDTO studentDTO = new StudentDTO();
studentDTO.setUserId(student.getId());
studentDTO.setUserName(student.getName());
studentDTO.setIdNumber(student.getName());
studentDTO.setDateOfBirth(student.getBirthday());
StudentFactory.postProcess2();
StudentFactory.postProcess();
return studentDTO;
}
}
}
测试
import com.ylz.mapstruct.domain.convert.impl.StudentConvertUtil;
import com.ylz.mapstruct.domain.dto.StudentDTO;
import com.ylz.mapstruct.domain.entity.Student;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class IStudentConvertTest {
@Test
void test() {
Student student = new Student();
student.setId(1111L);
student.setName("jack");
student.setIdCard("33333333333");
student.setBirthday(LocalDate.now());
System.out.println(StudentConvertUtil.INSTANCE.student2StudentDTO(student));
}
}
相对于第一种场景而言,
本场景将@BeforeMapping和@AfterMapping注解标注的方法移到了单独的类中,
然后在IStudentConvert接口中,
通过@Mapper注解的uses属性指定对应类的类型实现(可以同时指定多个类型