利用mapStruct 进行pojo类的转换
介绍
what
MapStruct 是一个代码生成器,它极大地简化了基于约定而不是配置方法的 Java Bean 类型之间映射的实现。生成的映射代码使用普通方法调用,因此快速、类型安全且易于理解。
why
多层应用程序通常需要在不同的对象模型(例如实体和 DTO)之间进行映射。编写此类映射代码是一项繁琐且容易出错的任务。MapStruct旨在通过尽可能地自动化来简化这项工作。对比BeanUtils ,该框架使用get、set方式实现,相较于反射效率更高,同时,支持特殊的映射关系,比BeanUtils更加灵活。
与其他映射框架相比,MapStruct 在编译时生成 Bean 映射,从而确保高性能,允许快速的开发人员反馈和彻底的错误检查。
how
MapStruct是一个注释处理器,它插入到Java编译器中,可以在命令行构建(Maven,Gradle等)以及IDE中使用。
官网:MapStruct – Java bean mappings, the easy way!
添加依赖
方案一 直接引入
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<option>true</option>
<version>1.4.2.Final</version>
</dependency>
Notice:
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId>
<version>${mapstruct.version}</version>
</dependency>
很多博客上写的 mapstruct-jdk8 坐标已在新版本中废弃
方案二 通过插件引入(官网)
注意: 新版本中 引入loombook需要同时添加 lombok-mapstruct-binding
<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<!-- 引用loombook需要额外引入的 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${org.projectlombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
IDE配置
另外 idea可以考虑安装MapStructSupport插件,在设置mapping的时候很好用
使用方法及DEMO
1.创建POJO
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ApiModel("用户卡新增修改")
public class AddModifyUserCardVo implements Serializable {
private static final long serialVersionUID = -7504607649930524455L;
•
@ApiModelProperty(value = "密码", example = "123456", required = true, position = -1, notes = "传加密后的密文,默认MD5(加密后密文是32位)加密方式")
private String password;
•
@ApiModelProperty(value = "", example = "风之诗", required = false)
private String tradeAccountName;
•
@ApiModelProperty(value = "电话号码", example = "15770634444", required = false)
private String phoneNo;
•
@ApiModelProperty(value = "通讯地址", example = "xx省xx市xx区xxx", required = false)
private String adddress;
}
/**
* 新增卡信息
*/
@ApiModel("客户卡信息")
@Data
public class CustUserParam implements Serializable {
•
private static final long serialVersionUID = -5708253734967403586L;
@ApiModelProperty(value = "密码", example = "123456", required = true, position = -1, notes = "传加密后的密文")
private String password;
•
@ApiModelProperty(value = "", example = "风之诗", required = false)
private String userName;
•
@ApiModelProperty(value = "电话号码", example = "15770634444", required = false)
private String phoneNo;
•
@ApiModelProperty(value = "通讯地址", example = "xx省xx市xx区xxx", required = false)
private String adddress;
}
2.创建转换Interface
@Mapper
public interface BankCardMapper {
BankCardMapper INSTANCT = Mappers.getMapper(BankCardMapper.class);
}
3.将Interface注入Spring
@Mapper(componentModel="spring") // 接口注入Spring,相当于加了一个@Component
public interface BankCardMapper {
/**
* 接口实现类的实例
*/
BankCardMapper INSTANCT = Mappers.getMapper(BankCardMapper.class);
CustBankParam addCardParamConvert(AddModifyBankCardVo addModifyBankCardVo);
}
componentModel 有四个可选项:
default: 默认 不注入,使用 Mappers.getMapper获取实例对象
spring:(常用)实现类上添加了@Component
cdi:
jsr330:生成的实现类上会添加@javax.inject.Named 和@Singleton,可以通过 @Inject注解获取
4.添加转换接口
@Mapper(componentModel="spring") //
public interface BankCardMapper {
CustBankParam addCardParamConvert(AddModifyBankCardVo addModifyBankCardVo);
}
5.在Servce层使用接口
注入mapper;
@Autowired
private BankCardMapper bankCardMapper;
public void dealService(AddModifyBankCardVo addModifyBankCardVo){
// ... 业务逻辑
CustBankParam custBankParam = bankCardMapper.addCardParamConvert(addModifyBankCardVo);
// ... 业务逻辑
}
6. 特殊转换
6.1 使用Mapping对属性进行特殊处理
@Mappings({
@Mapping(source = "tradeAccountName",target = "cusName"),// 不同名称之间转换
@Mapping(target = "password", ignore=true),//忽略密码,不进行映射
@Mapping(target = "createDt", expression = "java(new java.util.Date())"),//支持表达式
// 注意: 这个属性不能与source()、defaultValue()、defaultExpression()(可与source共同使用,当source的值为null的时候使用该表达式)、qualifiedBy()、qualifiedByName()或constant()一起使用。
@Mapping(target = "updateDt", dateFormat=”yyyy-MM-dd“),//时间戳转换
@Mapping(target = "captalMode", defaultValue="1"),//设置默认值,可设置source
})
CustBankParam addCardParamConvert(AddModifyBankCardVo addModifyBankCardVo);
6.2 逆映射
在双向映射的情况下,例如从实体到DTO以及从DTO到实体,前向方法和反向方法的映射规则通常是相似的,并且可以通过切换source和来简单地反转target。
使用注释 @InheritInverseConfiguration 表示方法应继承相应反向方法的反向配置
CustBankParam addCardParamConvert(AddModifyBankCardVo addModifyBankCardVo);
@InheritInverseConfiguration
AddModifyBankCardVo addModifyBankCardVoConvert(CustBankParam addModifyBankCardVo);
1)有一个注解@InheritConfiguration(下文提到)与该注解长得很像,请注意区分,虽然他们的功能有点相似,但是他们是不同的注解; 2)该注解虽然可以实现反转赋值,但是有一种情况需要手动加强——原映射的Mapping中没有source(典型的使用了expression、constant属性的都属于)。对于有这种情况的属性,原映射与新映射都需要指定出来;
3)该注解只有一个属性name,它的值是原映射名。你可以把它理解为原映射对应的方法的名字,即方法的名字就是映射名。如上面的例子,规则名是"addCardParamConvert"。那该注解可以这样写@InheritInverseConfiguration(name="addCardParamConvert")。
6.3利用已有的对象映射更新对象 @InheritConfiguration
使用方法与6.2类似,但功能上有些有差异,上面已经介绍,这里注意区分即可。
6.4(补充)公共属性的映射
对于我们平时新增、修改等操作,需要对修改create_time、update_time 等字段,有人说在修改数据库的时候使用mybatis-plus的自动填充即可,这也是个比较好的方案。可惜的是该框架不在我们公司的技术选型范围内,所以打算在类的转换上想办法了,如果在mapstruct上配置,这些字段需要对每个接口都配置一次,这显得很麻烦。能不能配置一个公共的方法就显得很重要。于是翻阅文档,发现了一个比较好玩的实验性功能: mapstruct.org/documentati…
上代码DEMO:
/**
* 新增时使用注解,入参需要添加 LoginUser loginUser
*/
@Retention(RetentionPolicy.CLASS)
@Mappings({
@Mapping(target = "createdTime",expression = "java(new java.sql.Timestamp(System.currentTimeMillis()))"),
@Mapping(target = "updatedTime",expression = "java(new java.sql.Timestamp(System.currentTimeMillis()))"),
@Mapping(target = "createdBy",source = "loginUser.unionUserId"),
@Mapping(target = "updatedBy",source = "loginUser.unionUserId"),
@Mapping(target = "tenantId",source = "loginUser.tenantId"),
@Mapping(target = "revision",expression = "java(new Integer(1))")
})
public @interface InsertMapper {
}
使用:
Mapper(componentModel = "spring")
public interface EduActivityMapper {
@InsertMapper
EduActivity insertEduActivity(EduActivityReq eduActivityReq, LoginUser loginUser);
后记
mapstruct的使用比起传统的BeanUtils更加灵活效率更高,但是每次转换都要新增一个接口,这是一个缺点吧,后续在使用中有什么心得,还会继续补充 不过最重要的还是要经常翻阅一下API吧