MapStruct 使用指南
1、安装与介绍
what?
mapstruct 是一个代码生成器,可以简化实现java bean 之间的转换的配置方法
生成的代码使用传统的方法实现get set属性,比起反射更快、更简单、更安全,易于理解
why?
基于多层的应用经常需要映射不同的对象模型 如VO -> TDO 等;属性转换的代码重复且容易出错。
与其他映射框架相比,MapStruct在编译时生成bean映射,这确保了高性能,允许快速的开发人员反馈和彻底的错误检查。
how?
MapStruct是一个注释处理器,插入Java编译器,通过在命令行构建(Maven,Gradle等)时使用。并且实现了默认的映射关系,故便于使用。
非spring环境下安装使用
参考官网设置:
MapStruct – Java bean mappings, the easy way!
spring环境下安装使用
pom.xml中增加mapstruct 的相关依赖
<!-- mapstruct 实体转换 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
在pom文件的<plugins>
标签中新增配置注解处理路径,项目中如果使用了lombok 需要注意其版本,如果版本高于 1.18.16,需要新增 lombok-mapstruct-binding
配置,兼容两者,低于 1.18.16 则不需要配置。
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.2.6.RELEASE</version>
<configuration>
<executable>true</executable>
<mainClass>org.test.application</mainClass>
</configuration>
</plugin>
<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>1.5.5.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</path>
<!-- This is needed when using Lombok 1.18.16 and above -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
最后是在IDEA 中安装mapstrut支持插件 mapStructSupport,该插件提供未映射字段提醒,写好映射文件后自动生成实现类,无需手动编译查看实现类的逻辑是否正确的功能
至此安装完成。
2、简单对象映射
简单对象映射定义为映射前后对象的基本成员都是基础数据类型,该类对象映射只需要映射转换前后对象的成员名称即可,详见以下示例:
现有转换之前的对象 OriginalOcrEntity
@Data
public class OriginalOcrEntity {
private String bill_number;
private String bill_code;
private String total_words;
private String payer;
List<ItemOriginal> items = new ArrayList<>();
}
需要将其转换为以下对象 ConvertOcrEntity
并且 orientation 字段值需要通过其他字段计算得到
@Data
@ToString
public class ConvertOcrEntity {
private String code;
private String number;
private String total_cn;
private String buyer;
/**
* 待计算字段
*/
private String orientation;
List<ItemConvert> items = new ArrayList<>();
需要编写一个映射接口:
1、接口上增加 @Mapper(componentModel = "spring" ) 将其交于spring 进行管理,这样可以支持直接@Autowired 获取实例,否则需要手动获取该接口的实例使用;
2、写转换方法,mapstruct 默认会将转换前后字段名一致的字段 get 到并 set 进新的对象中,只需要配置字段名不同的字段值即可;
使用 @Mappings({ }) 标注转换信息,内填写具体映射信息 @Mapping(target = "xx", source = "xx") ;
3、如果对象的成员对象是实例,或者List 则需要实现 对应实体 -> 新的实体的映射接口 以及List转换到新的List的接口,如 List
List<ItemConvert> ITEM_CONVERT_LIST(List<ItemOriginal> itemOriginals);
上面实现类的会生成调用下面接口的实现类的方法去做转换。
@Mappings({
@Mapping(target = "name", source = "project_name"),
@Mapping(target = "unit", source = "uom"),
@Mapping(target = "amount", source = "total")
})
ItemConvert ITEM_CONVERT(ItemOriginal itemOriginal);
最后完整的映射接口如下:
/**
* @ClassName Converter
* @Description 简单的对象转换
* @Date 2023/10/11
*/
@Mapper(componentModel = "spring" )
public interface CommonConverter {
@Mappings({
@Mapping(target = "code", source = "bill_code"),
@Mapping(target = "number", source = "bill_number"),
@Mapping(target = "total_cn", source = "total_words"),
@Mapping(target = "buyer", source = "payer"),
@Mapping(target = "items", source = "items")
})
ConvertOcrEntity CONVERT_OCR_ENTITY(OriginalOcrEntity entity);
List<ItemConvert> ITEM_CONVERT_LIST(List<ItemOriginal> itemOriginals);
@Mappings({
@Mapping(target = "name", source = "project_name"),
@Mapping(target = "unit", source = "uom"),
@Mapping(target = "amount", source = "total")
})
ItemConvert ITEM_CONVERT(ItemOriginal itemOriginal);
}
编写完成并保存后,idea如果安装了插件,会自动生成对应的代码,否则可能需要手动执行 maven complie 生成对应实现类;点击接口实现类图标,能够跳转到生成的实现类,该实现类的内容就是简单的get与 set 逻辑:
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-10-12T17:32:27+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_45 (Oracle Corporation)"
)
@Component
public class CommonConverterImpl implements CommonConverter {
@Override
public ConvertOcrEntity CONVERT_OCR_ENTITY(OriginalOcrEntity entity) {
if ( entity == null ) {
return null;
}
ConvertOcrEntity convertOcrEntity = new ConvertOcrEntity();
convertOcrEntity.setCode( entity.getBill_code() );
convertOcrEntity.setNumber( entity.getBill_number() );
convertOcrEntity.setTotal_cn( entity.getTotal_words() );
convertOcrEntity.setBuyer( entity.getPayer() );
// list对象转换 调用的 设置的 ITEM_CONVERT_LIST 接口的实现方法
convertOcrEntity.setItems( ITEM_CONVERT_LIST( entity.getItems() ) );
return convertOcrEntity;
}
@Override
public List<ItemConvert> ITEM_CONVERT_LIST(List<ItemOriginal> itemOriginals) {
if ( itemOriginals == null ) {
return null;
}
List<ItemConvert> list = new ArrayList<ItemConvert>( itemOriginals.size() );
for ( ItemOriginal itemOriginal : itemOriginals ) {
// list 实际上 为单个转换方法 ITEM_CONVERT ,汇总后并返回
list.add( ITEM_CONVERT( itemOriginal ) );
}
return list;
}
@Override
public ItemConvert ITEM_CONVERT(ItemOriginal itemOriginal) {
if ( itemOriginal == null ) {
return null;
}
ItemConvert itemConvert = new ItemConvert();
itemConvert.setName( itemOriginal.getProject_name() );
itemConvert.setUnit( itemOriginal.getUom() );
itemConvert.setAmount( itemOriginal.getTotal() );
return itemConvert;
}
}
可以看到,使用mapstruct 生成的实现类,无自己写的转换方法是一致的,能够减少大量写 get set方法的时间。
3、复杂对象映射
财务ocr识别结果转换的场景下存在复杂对象映射,主要针对,由于接口返回数据通常使用json 接收,并且部分字段值可能需要进行简单处理后才能保存到数据库中,json可以直接转为jsonObject ,jsonObject 底层使用的LinkedHashMap<String, Object> 存储的,故考虑直接传入LinkedHashMap<String,Object> 转换为所需对象,详见以下示例:
现通过接口返回的发票识别结果json串如下:
{
"code": "6300161320",
"number": "15064112",
"code_confirm": "333",
"number_confirm": "444",
"date": "111",
"pretax_amount": "222",
"total": "444",
"total_cn": "4123",
"tax": "123",
"check_code": "123",
"machine_code": "123",
"seller": "123",
"seller_tax_id": "123",
"seller_addr_tel": "123",
"seller_bank_account": "123",
"buyer": "123",
"buyer_tax_id": "123123",
"buyer_bank_account": "123123",
"buyer_addr_tel": "31231",
"company_seal": "123",
"form_type": "12313",
"form_name": "123",
"kind": "123",
"ciphertext": "123123",
"travel_tax": "123123",
"receiptor": "123",
"reviewer": "12313",
"issuer": "123",
"place": "123",
"province": "1231",
"city": "123",
"service_name": "132",
"remark": "123",
"item_names": "123",
"agent_mark": "12312",
"acquisition_mark": "123",
"block_chain": "12313",
"electronic_mark": "1231",
"transit_mark": "1231",
"oil_mark": "1231",
"vehicle_mark": "12312",
"title": "111",
"items": [
{
"name": "222",
"specification": "333",
"unit": "1231",
"quantity": "123",
"price": "123",
"total": "123",
"tax_rate": "123",
"tax": "1231"
}
]
}
需要将该json串转换为以下实体
@Data
public class Invoice10101 {
private String code;
private String number;
private String code_confirm;
private String number_confirm;
private String date;
private String pretax_amount;
private String total;
private String total_cn;
private String tax;
private String check_code;
private String machine_code;
private String seller;
private String seller_tax_id;
private String seller_addr_tel;
private String seller_bank_account;
private String buyer;
private String buyer_tax_id;
private String buyer_bank_account;
private String buyer_addr_tel;
private String company_seal;/
private String form_type;
private String form_name;
private String kind;
private String ciphertext;
private String travel_tax;
private String receiptor;
private String reviewer;
private String issuer;
private String place;
private String province;
private String city;
private String service_name;
private String remark;
private String item_names;
private String agent_mark;
private String acquisition_mark;
private String block_chain;
private String electronic_mark;
private String transit_mark;
private String oil_mark;
private String vehicle_mark;
private String title;
private List<Invoice10101Item> items;
}
Invoice10101Item 的结构如下:
@Data
public class Invoice10101Item {
/** "*保险服务*保费", --货物或应税劳务、服务名称 */
private String name;
private String specification;
private String unit;
private String quantity;
private String price;
private String total;
private String tax_rate;
private String tax;
}
自定义converter内容如下:
使用 @Mapping(target = "total", expression = "java( xxx )" 指定该字段的调用java 函数处理,该处需要指定处理类的全限定名进行调用
如 ConversionUtil.removeRmbSymbol() 传入String ,去掉符号¥ ,将返回值设置成指定的target字段的值
/**
* @InterfaceName Invoice10101Converter
* @Description 增值税发票 转换器
* @Date 2023/10/13
*/
@Mapper(componentModel = "spring")
public interface Invoice10101Converter {
@Mappings({
@Mapping(target = "pretax_amount", expression = "java( org.test.invoice.util.ConversionUtil.removeRmbSymbol((String) map.get(\"pretax_amount\")) )"),
@Mapping(target = "total", expression = "java( org.test.invoice.util.ConversionUtil.removeRmbSymbol((String) map.get(\"total\")) )"),
@Mapping(target = "tax", expression = "java( org.test.invoice.util.ConversionUtil.removeRmbSymbol((String) map.get(\"tax\")) )"),
@Mapping(target = "items", source = "items")
})
Invoice10101 CONVERT_OCR_ENTITY (LinkedHashMap<String, Object> map);
@Mappings({
@Mapping(target = "name", expression = "java( org.test.invoice.util.ConversionUtil.removeRmbSymbol((String) map.get(\"total\")) )"),
@Mapping(target = "tax", expression = "java( org.test.invoice.util.ConversionUtil.removeRmbSymbol((String) map.get(\"tax\")) )")
})
Invoice10101Item ITEM_CONVERT(Map<String, Object> map);
//默认情况使用 String 直接返回
default String map(Object o) {
return String.valueOf(o);
}
// 设置 List<item> 对象 需要自定义逻辑
default List<Invoice10101Item> CONVERTS(Object o) {
List<Invoice10101Item> itemConverts = new ArrayList<>();
JSONArray array = (JSONArray) o;
for (Object o1 : array) {
Map map = ((JSONObject) o1).toJavaObject(Map.class);
Invoice10101Item itemConvert = this.ITEM_CONVERT(map);
itemConverts.add(itemConvert);
}
return itemConverts;
}
}
测试转换结果:
--- 转换前
{
"code": "111",
"number": "222",
"code_confirm": "333",
"number_confirm": "444",
"date": "111",
...
"items": [
{
"name": "222",
"specification": "333",
"unit": "1231",
"quantity": "123",
"price": "123",
"total": "123",
"tax_rate": "123",
"tax": "1231"
}
]
}
--- 转换后
Invoice10101(code=111, number=222, code_confirm=333, number_confirm=444, date=111, pretax_amount=222, total=444, total_cn=4123, tax=123, check_code=123, machine_code=123, seller=123, seller_tax_id=123, seller_addr_tel=123, seller_bank_account=123, buyer=123, buyer_tax_id=123123, buyer_bank_account=123123, buyer_addr_tel=31231, company_seal=123, form_type=12313, form_name=123, kind=123, ciphertext=123123, travel_tax=123123, receiptor=123, reviewer=12313, issuer=123, place=123, province=1231, city=123, service_name=132, remark=123, item_names=123, agent_mark=12312, acquisition_mark=123, block_chain=12313, electronic_mark=1231, transit_mark=1231, oil_mark=1231, vehicle_mark=12312, title=111, items=[Invoice10101Item(name=123, specification=333, unit=1231, quantity=123, price=123, total=123, tax_rate=123, tax=1231)])
5、复用映射关系
有时部分映射关系可能被多个映射转换器使用,这时候可以提取该部分映射关系为一个新的converter,在其余使用到的接口文件引入提取的接口类,从而复用该部分映射关系。
提取item 转换关系到 ItemConverter ,并在CommonConverter 中uses 引入ItemConverter
/**
* @InterfaceName ItemConverter
* @Description ItemConverter
* @Date 2023/10/13
*/
@Mapper(componentModel = "spring" )
public interface ItemConverter {
@Mappings({
@Mapping(target = "name", source = "project_name"),
@Mapping(target = "unit", source = "uom"),
@Mapping(target = "amount", source = "total")
})
ItemConvert ITEM_CONVERT(ItemOriginal itemOriginal);
}
/**
* @ClassName Converter
* @Description 简单的对象转换
* @Date 2023/10/11
*/
@Mapper(componentModel = "spring" , uses = ItemConverter.class)
public interface CommonConverter {
@Mappings({
@Mapping(target = "code", source = "bill_code"),
@Mapping(target = "number", source = "bill_number"),
@Mapping(target = "total_cn", source = "total_words"),
@Mapping(target = "buyer", source = "payer"),
@Mapping(target = "items", source = "items")
})
ConvertOcrEntity CONVERT_OCR_ENTITY(OriginalOcrEntity entity);
List<ItemConvert> ITEM_CONVERT_LIST(List<ItemOriginal> itemOriginals);
检查生成的class 文件,可以看到 自动autowired了所需的ItemConverter
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2023-10-13T15:44:29+0800",
comments = "version: 1.5.5.Final, compiler: javac, environment: Java 1.8.0_45 (Oracle Corporation)"
)
@Component
public class CommonConverterImpl implements CommonConverter {
@Autowired
private ItemConverter itemConverter;
@Override
public ConvertOcrEntity CONVERT_OCR_ENTITY(OriginalOcrEntity entity) {
if ( entity == null ) {
return null;
}
ConvertOcrEntity convertOcrEntity = new ConvertOcrEntity();
convertOcrEntity.setCode( entity.getBill_code() );
convertOcrEntity.setNumber( entity.getBill_number() );
convertOcrEntity.setTotal_cn( entity.getTotal_words() );
convertOcrEntity.setBuyer( entity.getPayer() );
convertOcrEntity.setItems( ITEM_CONVERT_LIST( entity.getItems() ) );
return convertOcrEntity;
}
@Override
public List<ItemConvert> ITEM_CONVERT_LIST(List<ItemOriginal> itemOriginals) {
if ( itemOriginals == null ) {
return null;
}
List<ItemConvert> list = new ArrayList<ItemConvert>( itemOriginals.size() );
for ( ItemOriginal itemOriginal : itemOriginals ) {
list.add( itemConverter.ITEM_CONVERT( itemOriginal ) );
}
return list;
}
}
标签:指北,插件,String,tax,Mapping,private,123,MapStructs,target
From: https://www.cnblogs.com/charler/p/17928380.html