1 介绍
官网:https://mapstruct.org/,使用版本:1.4.2.Final
使用场景:pojo之间的相互转化
mapstruct:基于jsr269实现在编译期间生成代码,性能高,精细控制,解耦
orika:能够精细控制,解耦
org.springframework.beans.BeanUtils体系:简单易用,不能对属性进行定制处理
2 mapstruct使用
2.1 不使用框架的缺点
多而杂的代码与业务逻辑耦合,重复劳动等
使用前,依赖配置如下(mapstruct版本为1.4.2.Final,配置相关依赖有:mapstruct和mapstruct-processor):
<properties>
<mapstruct.version>1.4.2.Finalmapstruct.version>
properties>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.5.4version>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-validationartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>1.18.22version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstructartifactId>
<version>${mapstruct.version}version>
dependency>
<dependency>
<groupId>org.mapstructgroupId>
<artifactId>mapstruct-processorartifactId>
<version>${mapstruct.version}version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>1.3.2version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
dependencies>
先创建各个实体类:
FruitDTO(@Data已包含toString方法,可忽略):
package com.xiaoxu.boot.dto;
import lombok.Data;
import lombok.ToString;
import java.math.BigDecimal;
/**
* @author xiaoxu
* @date 2021-12-08
* spring_boot:com.xiaoxu.boot.dto.FruitDTO
*/
@Data
@ToString
public class FruitDTO {
/*
* 水果id
* */
private long fId;
/*
* 水果市场价格
* */
private double fPrice;
/*
* 水果实际价格
* */
private BigDecimal rPrice;
/*
* 水果名称
* */
private String fName;
}
ShopDTO:
package com.xiaoxu.boot.dto;
import lombok.Data;
import lombok.ToString;
import java.util.List;
/**
* @author xiaoxu
* @date 2021-12-08
* spring_boot:com.xiaoxu.boot.dto.MallDTO
*/
@Data
@ToString
public class ShopDTO {
/*
* 商店id
* */
private long sid;
/*
* 商店名称
* */
private String sname;
/*
* 商品内各个品种水果
* */
private List<FruitDTO> fruitDTOS;
/*
* 商店开张日期
* */
private String publishDate;
}
FruitVo:
package com.xiaoxu.boot.vo;
import lombok.Data;
import lombok.ToString;
/**
* @author xiaoxu
* @date 2021-12-08
* spring_boot:com.xiaoxu.boot.vo.FruitVo
*/
@Data
@ToString
public class FruitVo {
/*
* 水果id
* */
private long fruitId;
/*
* FruitVo 水果市场价格:保留两位小数(字符串)
* */
private String fruitPrice;
/*
* 水果实际价格:保留两位小数(字符串)
* */
private String realPrice;
/*
* 水果名称
* */
private String fruitName;
}
ShopVo:
package com.xiaoxu.boot.vo;
import lombok.Data;
import java.util.Date;
import java.util.List;
/**
* @author xiaoxu
* @date 2021-12-08
* spring_boot:com.xiaoxu.boot.vo.ShopVo
*/
@Data
public class ShopVo {
/*
* 商店id
* */
private long shopId;
/*
* 商店名称
* */
private String shopName;
/*
* 商品内各个品种水果
* */
private List<FruitVo> fruitVos;
/*
* 商店开张日期
* */
private Date publishDate;
/*
* 商店是否存在水果
* */
boolean hasFruit;
}
传统使用getter和setter转换实体类:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.FruitDTO;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.FruitVo;
import com.xiaoxu.boot.vo.ShopVo;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.traditionalConvertor
*/
public class traditionalConvertor {
public static void main(String[] args) throws ParseException {
ShopDTO shopDTO = traditionalConvertor.ShopDTOBuild();
System.out.println("shopDto:");
System.out.println(shopDTO+"\n");
ShopVo shopVo = traditionalConvertor.ShopVoBuild(shopDTO);
System.out.println("shopVo:");
System.out.println(shopVo);
}
private static ShopVo ShopVoBuild(ShopDTO shopDTO) throws ParseException {
//构造商店vo
ShopVo shopVo = new ShopVo();
shopVo.setShopId(shopDTO.getSid());
shopVo.setShopName(shopDTO.getSname());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
shopVo.setPublishDate(sdf.parse(shopDTO.getPublishDate()));
List<FruitVo> fruitVos = new ArrayList<>();
// 构造各个品种水果Vo
shopDTO.getFruitDTOS().forEach(fruitDTO -> {
FruitVo fruitVo=new FruitVo();
fruitVo.setFruitId(fruitDTO.getFId());
fruitVo.setFruitName(fruitDTO.getFName());
DecimalFormat decimalFormat = new DecimalFormat("#.00");
fruitVo.setFruitPrice(decimalFormat.format(fruitDTO.getFPrice()));
fruitVo.setRealPrice(decimalFormat.format(fruitDTO.getRPrice()));
fruitVos.add(fruitVo);
});
shopVo.setFruitVos(fruitVos);
shopVo.setHasFruit(shopVo.getFruitVos()!=null&&!shopVo.getFruitVos().isEmpty());
return shopVo;
}
private static ShopDTO ShopDTOBuild(){
//构造商店Dto
ShopDTO shopDTO=new ShopDTO();
shopDTO.setSid(12345L);
shopDTO.setSname("小天超市");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
shopDTO.setPublishDate(sdf.format(new Date()));
// 构造各个品种水果Dto
//第一个:
FruitDTO fruitDTO=new FruitDTO();
fruitDTO.setFId(1);
fruitDTO.setFPrice(12.5478d);
fruitDTO.setRPrice(new BigDecimal("10.35"));
fruitDTO.setFName("荔枝");
//第二个:
FruitDTO fruitDTO1 = new FruitDTO();
fruitDTO1.setFId(2);
fruitDTO1.setFPrice(19.9999d);
fruitDTO1.setRPrice(new BigDecimal("15.8"));
fruitDTO1.setFName("菠萝");
List<FruitDTO> fruitDTOList=new ArrayList<FruitDTO>(){{
add(fruitDTO);
add(fruitDTO1);
}};
shopDTO.setFruitDTOS(fruitDTOList);
return shopDTO;
}
}
结果如下:
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-02-28 19:41:05)
shopVo:
ShopVo(shopId=12345, shopName=小天超市, fruitVos=[FruitVo(fruitId=1, fruitPrice=12.55, realPrice=10.35, fruitName=荔枝), FruitVo(fruitId=2, fruitPrice=20.00, realPrice=15.80, fruitName=菠萝)], publishDate=Mon Feb 28 19:41:05 CST 2022, hasFruit=true)
package com.xiaoxu.convertor;
import org.mapstruct.Mapper;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.BaseConvertor
*/
@Mapper
public abstract class BaseConvertor {
Date parseDate(String date){
try{
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date);
}catch (ParseException p){
return null;
}
}
}
MapStructConvertor:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mapping(target = "publishDate",expression = "java(parseDate(shopDTO.getPublishDate()))")
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
}
MyConvertor:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.ShopVo;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MyConvertor
*/
public class MyConvertor {
public static void main(String[] args) {
ShopDTO shopDTO = traditionalConvertor.ShopDTOBuild();
System.out.println("shopDto:");
System.out.println(shopDTO+"\n");
ShopVo shopVo = MapStructConvertor.INSTANCE.shopDto2shopVo(shopDTO);
System.out.println("使用mapstruct后的shopVo:");
System.out.println(shopVo);
}
}
修改traditionalConvertor后:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.FruitDTO;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.FruitVo;
import com.xiaoxu.boot.vo.ShopVo;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.traditionalConvertor
*/
public class traditionalConvertor {
public static void main(String[] args) throws ParseException {
ShopDTO shopDTO = traditionalConvertor.ShopDTOBuild();
System.out.println("shopDto:");
System.out.println(shopDTO+"\n");
ShopVo shopVo = traditionalConvertor.ShopVoBuild(shopDTO);
System.out.println("shopVo:");
System.out.println(shopVo);
}
public static ShopVo ShopVoBuild(ShopDTO shopDTO) throws ParseException {
//构造商店vo
ShopVo shopVo = new ShopVo();
shopVo.setShopId(shopDTO.getSid());
shopVo.setShopName(shopDTO.getSname());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
shopVo.setPublishDate(sdf.parse(shopDTO.getPublishDate()));
List<FruitVo> fruitVos = new ArrayList<>();
// 构造各个品种水果Vo
shopDTO.getFruitDTOS().forEach(fruitDTO -> {
FruitVo fruitVo=new FruitVo();
fruitVo.setFruitId(fruitDTO.getFId());
fruitVo.setFruitName(fruitDTO.getFName());
DecimalFormat decimalFormat = new DecimalFormat("#.00");
fruitVo.setFruitPrice(decimalFormat.format(fruitDTO.getFPrice()));
fruitVo.setRealPrice(decimalFormat.format(fruitDTO.getRPrice()));
fruitVos.add(fruitVo);
});
shopVo.setFruitVos(fruitVos);
shopVo.setHasFruit(shopVo.getFruitVos()!=null&&!shopVo.getFruitVos().isEmpty());
return shopVo;
}
public static ShopDTO ShopDTOBuild(){
//构造商店Dto
ShopDTO shopDTO=new ShopDTO();
shopDTO.setSid(12345L);
shopDTO.setSname("小天超市");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
shopDTO.setPublishDate(sdf.format(new Date()));
// 构造各个品种水果Dto
//第一个:
FruitDTO fruitDTO=new FruitDTO();
fruitDTO.setFId(1);
fruitDTO.setFPrice(12.5478d);
fruitDTO.setRPrice(new BigDecimal("10.35"));
fruitDTO.setFName("荔枝");
//第二个:
FruitDTO fruitDTO1 = new FruitDTO();
fruitDTO1.setFId(2);
fruitDTO1.setFPrice(19.9999d);
fruitDTO1.setRPrice(new BigDecimal("15.8"));
fruitDTO1.setFName("菠萝");
List<FruitDTO> fruitDTOList=new ArrayList<FruitDTO>(){{
add(fruitDTO);
add(fruitDTO1);
}};
shopDTO.setFruitDTOS(fruitDTOList);
return shopDTO;
}
}
执行MyConvertor结果如下:
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-02-28 20:39:08)
使用mapstruct后的shopVo:
ShopVo(shopId=0, shopName=null, fruitVos=null, publishDate=Mon Feb 28 20:39:08 CST 2022, hasFruit=false)
2.3 @Mappings和@Mapping
指定属性之间的映射关系
默认映射规则:(1)同类型且同名的属性,会自动映射;(2)mapstruct会自动进行类型转换:1、8种基本类型和对应的包装类型之间会自动转换;2、8种基本类型(包含对应的包装类型),和String之间;3、日期类型和String之间
上述可知,有些字段默认是0,false或者null,因为字段名称不一致,默认mapstruct是不会进行映射的,故而需要增加字段映射:
修改MapStructConvertor,重新执行MyConvertor:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mapping(source = "sid",target = "shopId")
@Mapping(source = "sname",target = "shopName")
@Mapping(source = "fruitDTOS",target = "fruitVos")
@Mapping(target = "publishDate",expression = "java(parseDate(shopDTO.getPublishDate()))")
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
}
结果如下:
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-02-28 20:42:53)
使用mapstruct后的shopVo:
ShopVo(shopId=12345, shopName=小天超市, fruitVos=[FruitVo(fruitId=0, fruitPrice=null, realPrice=null, fruitName=null), FruitVo(fruitId=0, fruitPrice=null, realPrice=null, fruitName=null)], publishDate=Mon Feb 28 20:42:53 CST 2022, hasFruit=false)
上述的多个@Mapping,可以装在@Mappings中,效果一致,修改如下:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mappings(value = {
@Mapping(source = "sid",target = "shopId"),
@Mapping(source = "sname",target = "shopName"),
@Mapping(source = "fruitDTOS",target = "fruitVos"),
@Mapping(target = "publishDate",expression = "java(parseDate(shopDTO.getPublishDate()))")
})
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
}
且@Mapping的source和target参数中存在不存在的成员变量时,有提示:
日期类型和String之间可以默认转换,还可以加上日期格式化参数:
修改如下:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mappings(value = {
@Mapping(source = "sid",target = "shopId"),
@Mapping(source = "sname",target = "shopName"),
@Mapping(source = "fruitDTOS",target = "fruitVos"),
@Mapping(target = "publishDate",source = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss")
})
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
}
执行结果:
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-03-01 13:25:27)
使用mapstruct后的shopVo:
ShopVo(shopId=12345, shopName=小天超市, fruitVos=[FruitVo(fruitId=0, fruitPrice=null, realPrice=null, fruitName=null), FruitVo(fruitId=0, fruitPrice=null, realPrice=null, fruitName=null)], publishDate=Tue Mar 01 13:25:27 CST 2022, hasFruit=false)
使用数字格式化前,先对商店的水果List进行映射,在mapstruct中,对引用类型对象进行映射时,只需要在@Mapper修饰的抽象类或接口中,增加对应的抽象方法即可,如下:
修改如下:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.FruitDTO;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.FruitVo;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mappings(value = {
@Mapping(source = "sid",target = "shopId"),
@Mapping(source = "sname",target = "shopName"),
@Mapping(source = "fruitDTOS",target = "fruitVos"),
@Mapping(source = "publishDate",target = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss")
})
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
@Mappings(value = {
@Mapping(source = "FId",target = "fruitId"),
@Mapping(source = "FPrice",target = "fruitPrice"),
@Mapping(source = "RPrice",target = "realPrice"),
@Mapping(source = "FName",target = "fruitName")
})
public abstract FruitVo fruitDto2FruitVo(FruitDTO fruitDTO);
public abstract List<FruitVo> fruitDtos2FruitVos(List<FruitDTO> fruitDTO);
}
执行MyConvertor结果如下:
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-03-01 14:05:13)
使用mapstruct后的shopVo:
ShopVo(shopId=12345, shopName=小天超市, fruitVos=[FruitVo(fruitId=1, fruitPrice=12.5478, realPrice=10.35, fruitName=荔枝), FruitVo(fruitId=2, fruitPrice=19.9999, realPrice=15.8, fruitName=菠萝)], publishDate=Tue Mar 01 14:05:13 CST 2022, hasFruit=false
由此可知,List之间的对应,不要用@Mapping进行映射,只需要将引用对象之间的抽象方法对应(含字段的Mapping映射),再加上list之间对应的抽象方法即可。如果是单独的引用对象之间的映射,也是定义引用对象的映射抽象方法即可。
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-03-01 14:05:59)
使用mapstruct后的shopVo:
ShopVo(shopId=12345, shopName=小天超市, fruitVos=[FruitVo(fruitId=1, fruitPrice=12.55, realPrice=10.35, fruitName=荔枝), FruitVo(fruitId=2, fruitPrice=20.00, realPrice=15.8, fruitName=菠萝)], publishDate=Tue Mar 01 14:05:59 CST 2022, hasFruit=false)
因为list制定了Mapping映射,所以下面的List映射抽象方法,去掉不影响最终的结果:
如果有不需要映射转换的字段,那么@Mapping增加ignore参数即可,修改如下:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.FruitDTO;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.FruitVo;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mappings(value = {
@Mapping(source = "sid",target = "shopId"),
@Mapping(source = "sname",target = "shopName"),
@Mapping(source = "fruitDTOS",target = "fruitVos"),
@Mapping(source = "publishDate",target = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss"),
// @Mapping(target = "publishDate",expression = "java(parseDate(shopDTO.getPublishDate()))")
})
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
@Mappings(value = {
@Mapping(source = "FId",target = "fruitId",ignore = true),
@Mapping(source = "FPrice",target = "fruitPrice",numberFormat = "#.00"),
@Mapping(source = "RPrice",target = "realPrice"),
@Mapping(source = "FName",target = "fruitName")
})
public abstract FruitVo fruitDto2FruitVo(FruitDTO fruitDTO);
// public abstract List fruitDtos2FruitVos(List fruitDTO);
}
执行结果:
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-03-01 19:16:42)
使用mapstruct后的shopVo:
ShopVo(shopId=12345, shopName=小天超市, fruitVos=[FruitVo(fruitId=0, fruitPrice=12.55, realPrice=10.35, fruitName=荔枝), FruitVo(fruitId=0, fruitPrice=20.00, realPrice=15.8, fruitName=菠萝)], publishDate=Tue Mar 01 19:16:42 CST 2022, hasFruit=false)
2.4 @AfterMapping和@MappingTarget
在映射最后一步,对属性的自定义映射处理
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.FruitDTO;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.FruitVo;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mappings(value = {
@Mapping(source = "sid",target = "shopId"),
@Mapping(source = "sname",target = "shopName"),
@Mapping(source = "fruitDTOS",target = "fruitVos"),
@Mapping(source = "publishDate",target = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss"),
// @Mapping(target = "publishDate",expression = "java(parseDate(shopDTO.getPublishDate()))")
})
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
@Mappings(value = {
@Mapping(source = "FId",target = "fruitId",ignore = true),
@Mapping(source = "FPrice",target = "fruitPrice",numberFormat = "#.00"),
@Mapping(source = "RPrice",target = "realPrice"),
@Mapping(source = "FName",target = "fruitName")
})
public abstract FruitVo fruitDto2FruitVo(FruitDTO fruitDTO);
@AfterMapping
public void doAfter(ShopDTO shopDTO, @MappingTarget ShopVo shopVo){
List<FruitDTO> fruitDTOS = shopDTO.getFruitDTOS();
shopVo.setHasFruit(fruitDTOS!=null&&!fruitDTOS.isEmpty());
}
// public abstract List fruitDtos2FruitVos(List fruitDTO);
}
执行结果如下:
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-03-01 19:33:13)
使用mapstruct后的shopVo:
ShopVo(shopId=12345, shopName=小天超市, fruitVos=[FruitVo(fruitId=0, fruitPrice=12.55, realPrice=10.35, fruitName=荔枝), FruitVo(fruitId=0, fruitPrice=20.00, realPrice=15.8, fruitName=菠萝)], publishDate=Tue Mar 01 19:33:13 CST 2022, hasFruit=true)
2.5 @BeanMapping
ignoreByDefault:忽略mapstruct的默认映射行为。避免不需要的赋值、避免属性覆盖
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.FruitDTO;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.FruitVo;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mappings(value = {
@Mapping(source = "sid",target = "shopId"),
// @Mapping(source = "sname",target = "shopName"),
// @Mapping(source = "fruitDTOS",target = "fruitVos"),
// @Mapping(source = "publishDate",target = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss"),
// @Mapping(target = "publishDate",expression = "java(parseDate(shopDTO.getPublishDate()))")
})
@BeanMapping(ignoreByDefault = true)
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
@Mappings(value = {
@Mapping(source = "FId",target = "fruitId",ignore = true),
@Mapping(source = "FPrice",target = "fruitPrice",numberFormat = "#.00"),
@Mapping(source = "RPrice",target = "realPrice"),
@Mapping(source = "FName",target = "fruitName")
})
public abstract FruitVo fruitDto2FruitVo(FruitDTO fruitDTO);
@AfterMapping
public void doAfter(ShopDTO shopDTO, @MappingTarget ShopVo shopVo){
List<FruitDTO> fruitDTOS = shopDTO.getFruitDTOS();
shopVo.setHasFruit(fruitDTOS!=null&&!fruitDTOS.isEmpty());
}
// public abstract List fruitDtos2FruitVos(List fruitDTO);
}
执行结果如下:
可见,默认映射规则失效,只有@Mapping(source = “sid”,target = “shopId”),和doAfter生效:
shopDto:
ShopDTO(sid=12345, sname=小天超市, fruitDTOS=[FruitDTO(fId=1, fPrice=12.5478, rPrice=10.35, fName=荔枝), FruitDTO(fId=2, fPrice=19.9999, rPrice=15.8, fName=菠萝)], publishDate=2022-03-01 19:39:53)
使用mapstruct后的shopVo:
ShopVo(shopId=12345, shopName=null, fruitVos=null, publishDate=null, hasFruit=true)
2.6 结合spring
@Mapper(componentModel = “spring”)本质上,就是增加@Component注解。
代码如下:
package com.xiaoxu.convertor;
import com.xiaoxu.boot.dto.FruitDTO;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.FruitVo;
import com.xiaoxu.boot.vo.ShopVo;
import org.mapstruct.*;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* @author xiaoxu
* @date 2022-02-28
* spring_boot:com.xiaoxu.convertor.MapStructConvertor
*/
//可以接口或者抽象类,该接口或者类上增加注解@Mapper
@Mapper(componentModel = "spring")
public abstract class MapStructConvertor extends BaseConvertor{
public static final MapStructConvertor INSTANCE= Mappers.getMapper(MapStructConvertor.class);
/*
* shopDTO->shopVo
* */
@Mappings(value = {
@Mapping(source = "sid",target = "shopId"),
// @Mapping(source = "sname",target = "shopName"),
// @Mapping(source = "fruitDTOS",target = "fruitVos"),
// @Mapping(source = "publishDate",target = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss"),
// @Mapping(target = "publishDate",expression = "java(parseDate(shopDTO.getPublishDate()))")
})
@BeanMapping(ignoreByDefault = true)
public abstract ShopVo shopDto2shopVo(ShopDTO shopDTO);
@Mappings(value = {
@Mapping(source = "FId",target = "fruitId",ignore = true),
@Mapping(source = "FPrice",target = "fruitPrice",numberFormat = "#.00"),
@Mapping(source = "RPrice",target = "realPrice"),
@Mapping(source = "FName",target = "fruitName")
})
public abstract FruitVo fruitDto2FruitVo(FruitDTO fruitDTO);
@AfterMapping
public void doAfter(ShopDTO shopDTO, @MappingTarget ShopVo shopVo){
List<FruitDTO> fruitDTOS = shopDTO.getFruitDTOS();
shopVo.setHasFruit(fruitDTOS!=null&&!fruitDTOS.isEmpty());
}
// public abstract List fruitDtos2FruitVos(List fruitDTO);
}
单测类如下:
import com.xiaoxu.boot.MainApplication;
import com.xiaoxu.boot.dto.ShopDTO;
import com.xiaoxu.boot.vo.ShopVo;
import com.xiaoxu.convertor.MapStructConvertor;
import com.xiaoxu.convertor.traditionalConvertor;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @author xiaoxu
* @date 2022-03-01
* spring_boot:PACKAGE_NAME.TestMapSct
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MainApplication.class)
public class TestMapSct {
@Autowired
MapStructConvertor mapStructConvertor;
@Test
public void test_01(){
ShopDTO shopDTO = traditionalConvertor.ShopDTOBuild();
ShopVo shopVo = mapStructConvertor.shopDto2shopVo(shopDTO);
System.out.println(shopVo);
}
}
结果如下:
ShopVo(shopId=12345, shopName=null, fruitVos=null, publishDate=null, hasFruit=true)
注意:这里主程序类和MyConvertor不在同个包路径下,执行SpringBoot的单测时,需要给启动类增加扫描路径:
如此单测才可顺利运行。