1.概述
在本教程中,我们将探索使用映射,这是简单地放了java bean映射器。
这个API包含自动映射两个Java bean的函数。使用MapStruct,我们只需要创建接口,库将在编译期间自动创建具体的实现。
进一步阅读:
2.MapStruct和传输对象模式
对于大多数应用程序,您会注意到许多将pojo转换为其他pojo的样板代码。
例如,一种常见类型的转换发生在持久性支持的实体和发送到客户端的dto之间。
所以,这就是MapStruct要解决的问题:手动创建bean映射器非常耗时。但是图书馆可以自动生成bean映射器类。
3.Maven
让我们将下面的依赖项添加到Maven中pom.xml:
<依赖项> org.mapstruct groupID> MAPSTRUCT ARTIFACTID> 1.3.1.final version> 依赖项>
的最新稳定版本映射和它的处理器都可以从Maven中央存储库中获得。
我们再加上AnnotationProcessorPaths.章节的配置部分maven-compiler-plugin插件。
的mapstruct-processor用于在构建期间生成映射器实现:
org.apache.maven.plugins groupID> Maven-Compiler-Plugin ARTIFACTID> 3.5.1 Version> 1.8 source> 1.8 target> org.mapstruct groupId> Mapstruct-Processor Artifactid> 1.3.1.final Version> path> AnnotationProcessorPaths> configuration> plugin>
4.基本的映射
4.1。创建一个POJO
让我们首先创建一个简单的java pojo:
public class SimpleSource {private String name;私人字符串描述;// getters和setters} public class SimpleDestination {private String name;私人字符串描述;//设置函数
4.2。映射器接口
@mapper公共界面SimplesourceDestinationMapper {SimpleStination Sourcetodestination(SimpleSource Source);SimpleSource DestinationToSource(SimpleStinal Destination);}
请注意,我们没有为SimpleSourceDestinationMapper -因为MapStruct为我们创建了它。
4.3。新的映射器
可以通过执行一个mvn全新安装.
这将生成下面的实现类/目标/生成的源/注释/.
下面是MapStruct为我们自动创建的类:
public class SimpleSourceDestinationMapperImpl implements SimpleSourceDestinationMapper {@Override public simplesdestination sourceToDestination(SimpleSource source) {if (source == null) {return null;} SimpleDestination SimpleDestination = new SimpleDestination();simpleDestination。setName (source.getName ());simpleDestination。setDescription (source.getDescription ());返回simpleDestination;} @Override public SimpleSource destinationToSource(SimpleDestination destination){if (destination == null){return null;} SimpleSource SimpleSource = new SimpleSource();simpleSource。setName( destination.getName() ); simpleSource.setDescription( destination.getDescription() ); return simpleSource; } }
4.4。测试用例
最后,通过一切生成,让我们写一个测试案例,显示值SimpleSource.匹配值SimpleDestination:
public class SimpleSourceDestinationMapperIntegrationTest {private SimpleSourceDestinationMapper = mapper . getmapper (SimpleSourceDestinationMapper.class);@Test public void givenSourceToDestination_whenMaps_thenCorrect() {SimpleSource SimpleSource = new SimpleSource();simpleSource.setName(“SourceName”);simpleSource.setDescription(“SourceDescription”);mapper.sourceToDestination(simpleSource);destination.getName assertequal (simpleSource.getName () ());destination.getDescription assertequal (simpleSource.getDescription () ());} @Test public void givenDestinationToSource_whenMaps_thenCorrect() {SimpleDestination destination = new SimpleDestination();destination.setName(“DestinationName”);destination.setDescription(“DestinationDescription”); SimpleSource source = mapper.destinationToSource(destination); assertEquals(destination.getName(), source.getName()); assertEquals(destination.getDescription(), source.getDescription()); } }
5.使用依赖注入的映射
接下来,让我们仅通过调用获取Mapstruct中的映射器的实例Mappers.getMapper (YourClass.class).
当然,这是获取实例的一种非常手动的方式。然而,一个更好的替代方案是直接在需要的地方注入映射器(如果我们的项目使用任何依赖注入解决方案)。
幸运的是,MapStruct对Spring和CDI都有坚实的支持(背景和依赖注入)。
要在映射器中使用Spring IoC,需要添加componentModel属性来@Mapper与价值春天,而对于CDI,则是cdi.
5.1。修改映射器
将以下代码添加到SimpleSourceDestinationMapper.:
@Mapper(componentModel = "spring")公共接口
6.映射不同字段名的字段
在前面的示例中,MapStruct能够自动映射bean,因为它们具有相同的字段名。那么,如果我们要映射的bean有一个不同的字段名呢?金宝搏官网188be
在本例中,我们将创建一个名为员工和聘用.
6.1。新pojo
public class EmployeeDTO {private int employeeId;私人字符串employeeName;//设置函数
public class Employee {private int id;私人字符串名称;//设置函数
6.2。映射器接口
映射不同的字段名称时,我们需要将其源字段配置为其目标字段,并执行此操作,我们需要添加@Mappings注释。该注释接受的数组为@Mapping注释,我们将使用它来添加目标和源属性。
在MapStruct中,我们也可以使用点表示法来定义bean的成员:
@Mapper public interface EmployeeMapper {@Mappings({@Mapping(target="employeeId", source="entity.id"), @Mapping(target="employeeName", source="entity.name")}) EmployeeDTO employeeToEmployeeDTO(雇员实体);@Mapping({@Mapping(target="id", source="dto. employeeid "), @Mapping(target="name", source="dto. employeename ")}) Employee employeeDTOtoEmployee(EmployeeDTO dto);}
6.3。测试用例
再次,我们需要测试两个源和目标对象值匹配:
@Test public void givenEmployeeDTOwithDiffNametoEmployee_whenMaps_thenCorrect() {EmployeeDTO dto = new EmployeeDTO();dto.setEmployeeId (1);dto.setEmployeeName(“约翰”);Employee entity = mapper.employeeDTOtoEmployee(dto);entity.getId assertequal (dto.getEmployeeId () ());entity.getName assertequal (dto.getEmployeeName () ());}
中可以找到更多的测试用例GitHub项目.
7.映射bean与子bean
接下来,我们将展示如何将引用映射到其他bean。
7.1。修改POJO
来添加一个新的bean引用员工目的:
public class EmployeeDTO {private int employeeId;私人字符串employeeName;私人DivisionDTO部门;//省略getter和setter}
public class Employee {private int id;私人字符串名称;私人部门部;//省略getter和setter}
public class Division {private int id;私人字符串名称;//默认构造函数,省略了getter和setter}
7.2。修改映射器
在这里,我们需要添加一种转换方法来转换分配来分区反之亦然;如果Mapstruct检测到需要转换对象类型并且在同一类中存在转换方法,则它将自动使用它。
让我们把这个添加到映射器中:
DivisionDTO divisionToDivisionDTO(业务实体);部门divisionDTOtoDivision (DivisionDTO dto);
7.3。修改测试用例
让我们修改并将一些测试用例添加到现有的一个:
@Test public void givenEmpDTONestedMappingToEmp_whenMaps_thenCorrect() {EmployeeDTO dto = new EmployeeDTO();dto。setDivision(新DivisionDTO(1“Division1”));Employee entity = mapper.employeeDTOtoEmployee(dto);assertequal (dto.getDivision () .getId (), entity.getDivision () .getId ());assertequal (dto.getDivision () . getname (), entity.getDivision () . getname ());}
8.类型转换的映射
Mapstruct还提供了一些现成的隐式类型转换,而对于我们的示例,我们将尝试将字符串日期转换为实际日期目的。
有关隐式类型转换的更多详细信息,请查看MapStruct参考指南.
8.1。修改豆类
我们为员工添加了一个开始工作的日期:
public class Employee{//其他字段private Date startDt;//设置函数
public class EmployeeDTO{//其他字段private String employeeStartDt;//设置函数
8.2。修改映射器
我们修改映射器并提供dateFormat我们的开始日期:
@Mapping({@Mapping(target="employeeName", source =" entity.id"), @Mapping(target="employeeName", source =" entity.name"), @Mapping(target="employeeStartDt", source =" entity.id"), @Mapping(target="employeeStartDt", source =" entity.id"), @Mapping(target="employeeStartDt", source =" entity.id"), @Mapping(target="employeeStartDt", source =" entity.id"),yyyy HH:mm:ss")}) EmployeeDTO employeeToEmployeeDTO(Employee entity);@Mapping({@Mapping(target="id", source="dto. employeeid "), @Mapping(target="name", source="dto. employeename "), @Mapping(target="startDt", source="dto. employeename "), @Mapping(target="startDt", source="dto. employeename "),employeeStartDt", dateFormat="dd-MM-yyyy HH:mm:ss")})
8.3。修改测试用例
让我们添加更多的测试用例来验证转换是否正确:
private static final String DATE_FORMAT = "dd-MM-yyyy HH:mm:ss";@Test public void givenEmpStartDtMappingToEmpDTO_whenMaps_thenCorrect() throws ParseException{雇员实体=新雇员();实体。setStartDt(新日期());EmployeeDTO = mapper.employeeToEmployeeDTO(实体);SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);assertequal (format.parse (dto.getEmployeeStartDt ()) .toString (), entity.getStartDt () .toString ());} @Test public void givenEmpDTOStartDtMappingToEmp_whenMaps_thenCorrect() throws ParseException {EmployeeDTO dto = new EmployeeDTO();dto。setEmployeeStartDt(“01-04-2016 01:00:00”); Employee entity = mapper.employeeDTOtoEmployee(dto); SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT); assertEquals(format.parse(dto.getEmployeeStartDt()).toString(), entity.getStartDt().toString()); }
9.使用抽象类进行映射
有时,我们可能想要以超出@Mapping功能的方式定制映射器。
例如,除了类型转换外,我们可能还希望以下面示例中的某种方式转换值。
在这种情况下,我们可以创建一个抽象类和实现我们想要定制的方法,并留下映射生成的方法。
9.1。基本模型
在此示例中,我们将使用以下类:
public class Transaction {private Long id;private String uuid = uuid . randomuuid ().toString();私人BigDecimal总;/ /标准getter}
和匹配的DTO:
公共类TransactionDto {私有字符串UUID;私人长的总体;//标准getters和setter}
这里的难点是转换大二世全部的数额的美元变成长totalInCents.
9.2。定义一个映射器
我们可以通过创造我们的映射器作为抽象类:
@Mapper抽象类TransactionMapper {public TransactionDTO toTransactionDTO(Transaction Transaction) {TransactionDTO TransactionDTO = new TransactionDTO();transactionDTO.setUuid (transaction.getUuid ());transactionDTO.setTotalInCents (transaction.getTotal () .multiply(新BigDecimal (" 100 ")) .longValue ());返回transactionDTO;}公共抽象列表 toTransactionDTO(集合<事务>事务);}
在这里,我们已经为单个对象转换实现了完全定制的映射方法。
另一方面,我们离开了该方法,这意味着映射收藏到A.列表抽象的,所以映射将为我们实施。
9.3。生成的结果
由于我们已经实现了映射单个的方法交易进入A.TransactionDTO,我们预计映射在第二种方法中使用它。
将生成以下文件:
@ created class TransactionMapperImpl extends TransactionMapper {@Override public List toTransactionDTO(Collection transactions) {if (transactions == null) {return null;} List List = new ArrayList<>();for (Transaction Transaction: transactions) {list。add(toTransactionDTO(transaction));}返回列表;}}
正如我们在第12行看到的,映射在它生成的方法中使用我们的实现。
10.映射前标注和映射后标注
这是自定义的另一种方法@Mapping通过使用功能@BeforeMapping和@AfterMapping注释。注释用于标记在映射逻辑之前和之后调用的方法。
他们在我们可能想要的情况下非常有用应用于所有映射超类型的行为。
让我们看一个映射的子类型的示例车电子车和BioDieselCar来CarDTO.
在映射时,我们希望将类型的概念映射到FuelTypeDTO中的枚举字段。然后,在完成映射之后,我们希望将DTO的名称更改为大写。
10.1。基本模型
我们将使用以下类:
public class Car {private int id;私人字符串名称;}
的子类型车:
public class BioDieselCar extends Car {}
public class ElectricCar extends Car {}
的CarDTO枚举现场类型FuelType:
public class CarDTO {private int id;私人字符串名称;私人FuelType FuelType;}
Public Enum Fueltype {Electric,Bio_diesel}
10.2。定义映射器
现在让我们继续编写用于映射的抽象mapper类车来CarDTO:
@Mapper public抽象类CarsMapper {@BeforeMapping protected void fuldtowithfueltype (Car Car, @MappingTarget CarDTO CarDTO) {if (Car instanceof ElectricCar) {CarDTO . setfueltype (FuelType.ELECTRIC);} if (car instanceof BioDieselCar) {carDto.setFuelType(FuelType.BIO_DIESEL);}} @AfterMapping protected void convertNameToUpperCase(@MappingTarget CarDTO CarDTO) {CarDTO . setname (CarDTO . getname ().toUpperCase());} public abstract CarDTO toCarDto(Car Car);}
@MappingTarget参数注释是这样的吗在执行映射逻辑之前填充目标映射DTO在的情况下@BeforeMapping和就在之后以防@AfterMapping注释方法。
10.3。结果
的Carsmapper.上面定义的生成的执行:
@Generated public class CarsMapperImpl extends CarsMapper {@Override public CarDTO toCarDto(Car Car) {if (Car == null) {return null;} CarDTO CarDTO = new CarDTO();enrichDTOWithFuelType(汽车,carDTO);carDTO.setId (car.getId ());carDTO.setName (car.getName ());convertNameToUpperCase (carDTO);返回carDTO;}}
注意带注释的方法调用围绕着映射逻辑在实施中。
11.支持龙目岛
在最近的Mapstruct版本中,龙目岛支持已宣布。因此,我们可以轻松地使用Lombok映射源实体和目的地。
要启用Lombok支持,我们需要添加依赖在注释处理器路径中。现在我们有MapStruct-Processor以及Maven Compiler插件中的龙眼洛克:
org.apache.maven.plugins groupID> Maven-Compiler-Plugin ARTIFACTID> 3.5.1 Version> 1.8 source> 1.8 target> org.mapstruct groupId> Mapstruct-Processor Artifactid> 1.3.1.final Version> path> org.projectlombok lombok 1.18.4
让我们使用Lombok注释定义源实体:
@Getter @Setter public class Car {private int id;私人字符串名称;}
和目标数据传输对象:
@petter @Setter公共类Cardto {private int id;私人字符串名称;}
它的映射器接口仍然类似于我们前面的例子:
@Mapper公共接口CarMapper {CarMapper实例= mapper . getmapper (CarMapper.class);CarDTO carToCarDTO汽车(汽车);}
12.支持defaultExpression
从版本1.3.0开始,我们可以使用defaultExpression的属性@Mapping注释以指定表达式,该表达式在源字段为时确定目标字段的值无效的.这是现有的defaultValue属性的功能。
源实体:
public class Person {private int id;私人字符串名称;}
目标数据传输对象:
public class PersonDTO {private int id;私人字符串名称;}
如果id字段的源实体无效的,我们想生成一个随机的id并将其赋给目标,保持其他属性值不变:
@mapper公共接口人物绘图{personmapper实例= mappers.getmapper(personmapper.class);@mapping(target =“id”,source =“person.id”,defaultexpression =“java(java.util.uuuid.randomuuuid()。ToString())”)珀尔登特人物(人);}
让我们添加一个测试用例来验证表达式的执行:
@test public void给oderpersonentitytopersonwithexpression_whenmaps_thencorrect()persentity = new person();Entity.SetName(“Micheal”);珀尔登珀尔= personmapper.instance.persontopersondto(实体);assertnull(entity.getId());assertnotnull(persondto.getId());assertequals(persondto.getname(),Entity.getName());}
13.结论
本文介绍了MapStruct。我们介绍了Mapping库的大部分基础知识,以及如何在应用程序中使用它。
这些示例和测试的实施可以在其中找到GitHub项目。这是一个Maven项目,所以应该易于导入和运行。