MapStruct 使用示例
官网地址:MapStruct
官方示例:mapstruct-examples
关联:
个人实践:SpringBoot-Labs-Junw 中的 jLab-2-MapStruct-jdk17 jLab-2-MapStruct-jdk8
安装/依赖
<properties>
<java.version>17</java.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<lombok.version>1.18.32</lombok.version>
<lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>${swagger2.version}</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler-plugin.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<release>>${java.version}</release>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<!--兼容插件-->
<artifactId>lombok-mapstruct-binding</artifactId>
<version>${lombok-mapstruct-binding.version}</version>
</path>
</annotationProcessorPaths>
<!--在编译时打印相关转换信息-->
<showWarnings>true</showWarnings>
<compilerArgs>
<compilerArg>
<!--在编译时打印相关转换信息-->
-Amapstruct.verbose=true
</compilerArg>
<compilerArg>
<!-- 以spring注入的方式访问mapper-->
-Amapstruct.defaultComponentModel=spring
</compilerArg>
<compilerArg>
<!-- 注入方式 默认是字段注入 设置为constructor是构造器注入-->
-Amapstruct.defaultInjectionStrategy=field
</compilerArg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
@Mapper注解上的componentModel(1)和injectionStrategy(2)会覆盖默认配置中的设置。
componentModel 基于spring通常这里不需要覆盖默认配置
InjectionStrategy.CONSTRUCTOR可以设置基于构造器注入,覆盖默认配置的字段注入
1. 属性名相同
1.1 实体
@Data
public class SourceVO {
private String test;
}
1.2 接口
@Mapper
public interface SourceToSource {
SourceToSource INSTANCE = Mappers.getMapper( SourceToSource.class );
SourceVO toSource(SourceVO s );
}
1.3 测试用例
@Test
public void sourceToSourceTest() {
SourceVO s = new SourceVO();
s.setTest( "5" );
SourceVO newS = SourceToSource.INSTANCE.toSource( s );
assertEquals( "5", newS.getTest() );
}
2. 属性名不同
2.1 实体
@Data
public class SourceVO {
private String test;
}
@Data
public class TargetVO {
private Long testing;
}
2.2 接口
@Mapper
public interface SourceToTarget {
SourceToTarget INSTANCE = Mappers.getMapper( SourceToTarget.class );
@Mapping( source = "test", target = "testing" )
TargetVO toTarget(SourceVO s );
}
2.3 测试用例
@Test
public void sourceToTargetTest() {
SourceVO s = new SourceVO();
s.setTest( "5" );
TargetVO t = SourceToTarget.INSTANCE.toTarget( s );
assertEquals( 5, (long) t.getTesting() );
}
3. 多参数源
3.1 实体
@Data
@AllArgsConstructor
public class ManVO {
private String name;
private Integer age;
}
@Data
@AllArgsConstructor
public class WomanVO {
private String name;
private Integer age;
}
@Data
public class HomeVO {
private String manName;
private String womanName;
private Integer manAge;
private Integer womanAge;
}
3.2 接口
@Mapper
public interface ManWomanToHome {
ManWomanToHome INSTANCE = Mappers.getMapper( ManWomanToHome.class );
@Mappings({
@Mapping( source = "man.name", target = "manName" ),
@Mapping( source = "man.age", target = "manAge" ),
@Mapping( source = "woman.name", target = "womanName" ),
@Mapping( source = "woman.age", target = "womanAge" )
})
HomeVO toHome(ManVO man, WomanVO woman);
}
3.3 测试用例
@Test
public void multiParamsTest() {
ManVO man = new ManVO("小帅",24);
WomanVO woman = new WomanVO("小美",21);
HomeVO home = ManWomanToHome.INSTANCE.toHome( man, woman);
assertEquals( "HomeVO(manName=小帅, womanName=小美, manAge=24, womanAge=21)", home.toString() );
}
4. 多参数类型
4.1 实体
@Data
@AllArgsConstructor
public class ChildEO {
private String bname;
private int bAge;
}
@Data
@AllArgsConstructor
public class ChildVO {
private String bname;
private int bAge;
}
@Data
public class SourceEntity {
private int intValue;
private Integer integerValue;
private BigDecimal bigDecimalValue;
private LocalDateTime localDateTimeValue;
private Date dateValue;
private MyEnum enumValue;
private java.sql.Timestamp timestampValue;
private String stringValue;
private String ignore;
private ChildVO baby;
private String sourceId;
}
@Data
public class TargetEntity {
private String intValueStr;
private int intValue;
private String formattedBigDecimal;
private String localDateTimeValueStr;
private String formattedDate;
private String enumValueAsString;
private Date timestampAsDate;
private String stringValue;
private String constantStr;
private String expression_str;
private String expression_date;
private String expression_method;
private String ignoreStr;
private String expression_util;
private ChildEO baby;
private String id;
}
public enum MyEnum {
VALUE_1(0, "正常"),
VALUE_2(1, "已删除");
private int value;
private String label;
private MyEnum(int value, String label) {
this.value = value;
this.label = label;
}
public int getValue() {
return this.value;
}
public String getLabel() {
return this.label;
}
}
4.2 接口
@Mapper(imports = {StringBOUtils.class})
public interface EntityMapper {
EntityMapper INSTANCE = Mappers.getMapper(EntityMapper.class );
// 定义映射方法,指定映射规则
@Mappings({
// 基本类型 --> String
@Mapping(source = "intValue", target = "intValueStr"),
// 基本类型包装类 --> 基本类型
@Mapping(source = "integerValue", target = "intValue"),
// LocalDateTime --> String
@Mapping(source = "localDateTimeValue", target = "localDateTimeValueStr"),
// BigDecimal --> String 保留两位小数
@Mapping(source = "bigDecimalValue", target = "formattedBigDecimal", numberFormat = "#.00"),
// Date --> String 指定时间格式 yyyy-MM-dd HH:mm:ss
@Mapping(source = "dateValue", target = "formattedDate", dateFormat = "yyyy-MM-dd HH:mm:ss"),
// 枚举类 --> String
@Mapping(source = "enumValue", target = "enumValueAsString"),
// Timestamp --> Date
@Mapping(source = "timestampValue", target = "timestampAsDate"),
// defaultValue 设置默认值,来源为null时,目标为默认值。
@Mapping(source = "stringValue", target = "stringValue",defaultValue = "Hello"),
// 目标值指定为常量
@Mapping(target = "constantStr",constant = "我是常量"),
// ignore = true 忽略属性,不拷贝
@Mapping(target = "ignoreStr",ignore = true),
// expression 表达式:常量
@Mapping(target = "expression_str",expression = "java(\"Jaws\")"),
// expression 表达式:一段代码
@Mapping(target = "expression_date",expression = "java(\"表达式 --> 时间:\" + new java.util.Date())"),
// expression 表达式:方法调用-当前类
@Mapping(target = "expression_method",expression = "java(EntityMapper.getLastRunTime(sourceEntity))"),
// expression 表达式:方法调用-外部工具类
@Mapping(target = "expression_util", expression = "java(StringBOUtils.toBOString(sourceEntity.getIgnore()))"),
// 嵌套-子对象属性完全一致,可以用.
@Mapping(target = ".", source = "baby"),
// 默认表达式,仅source值为null时生效
@Mapping(target="id", source="sourceId", defaultExpression = "java(\"UUID\" )")
})
TargetEntity mapToTarget(SourceEntity sourceEntity);
static String getLastRunTime(SourceEntity sourceEntity){
return "lastRunTime --> " + sourceEntity.getDateValue();
}
}
4.3 其他类
public class StringBOUtils {
public static String toBOString(String poString) {
return poString + "BO";
}
}
4.4 测试用例
public class MultiParamTypeTest {
/**
* 多参数源
*/
@Test
public void multiParamTypeTest() {
// 创建源实体对象
SourceEntity sourceEntity = new SourceEntity();
sourceEntity.setIntValue(10);
sourceEntity.setIntegerValue(12);
sourceEntity.setLocalDateTimeValue(LocalDateTime.now());
sourceEntity.setBigDecimalValue(new BigDecimal("123.456"));
sourceEntity.setDateValue(new Date());
sourceEntity.setEnumValue(MyEnum.VALUE_1);
sourceEntity.setTimestampValue(new Timestamp(System.currentTimeMillis()));
// sourceEntity.setStringValue("Hello");
sourceEntity.setIgnore("ignore");
ChildVO babyVO = new ChildVO("baby",1);
sourceEntity.setBaby(babyVO);
// 调用映射方法,将源实体映射为目标实体
TargetEntity targetEntity = EntityMapper.INSTANCE.mapToTarget(sourceEntity);
System.out.println(targetEntity.toString());
// 验证映射结果
assert "10".equals(targetEntity.getIntValueStr());
assert targetEntity.getIntValue() == 12;
assert targetEntity.getStringValue().equals("Hello");
assert targetEntity.getFormattedBigDecimal().equals("123.46"); // 根据规则,保留两位小数
// 验证日期格式化
// 在此为了简化,这里不验证具体格式,只验证是否为非空字符串
assert targetEntity.getFormattedDate() != null && !targetEntity.getFormattedDate().isEmpty();
assert targetEntity.getEnumValueAsString().equals(MyEnum.VALUE_1.toString());
assert targetEntity.getTimestampAsDate() != null;
}
}
5. 注解应用示例
5.1 实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDTO {
private Long id;
private String fullName;
private String email;
}
@Data
public class UserEntity {
private Long id;
private String firstName;
private String lastName;
private String email;
private String address;
}
5.2 接口
@Mapper(componentModel = "spring")
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
// 基本映射
@Mapping(source = "firstName", target = "fullName", qualifiedByName = "fullNameMapping")
@Mapping(source = "email", target = "email")
@BeanMapping(ignoreByDefault = true) // 使用 @BeanMapping 忽略未映射的属性
UserDTO toDTO(UserEntity userEntity, @Context MappingContext context);
// 逆向映射
@InheritInverseConfiguration
UserEntity toEntity(UserDTO userDTO, @Context MappingContext context);
// 更新目标对象
@BeanMapping(ignoreByDefault = true) // 使用 @BeanMapping 忽略未映射的属性
@Mapping(target = "firstName", expression = "java(userDTO.getFullName().split(\" \")[0])")
@Mapping(target = "lastName", ignore = true)
void updateEntityFromDTO(UserDTO userDTO, @MappingTarget UserEntity userEntity, @Context MappingContext context);
// 映射前的操作
@BeforeMapping
default void beforeMapping(@MappingTarget UserEntity userEntity, @Context MappingContext context) {
// 在映射前执行一些操作
System.out.println("BeforeMapping -->" + context.pContext());
}
// 映射后的操作
@AfterMapping
default void afterMapping(@MappingTarget UserDTO userDTO, @Context MappingContext context) {
// 在映射后执行一些操作
System.out.println("AfterMapping -->" + context.pContext());
}
// 使用自定义方法进行映射
@Named("fullNameMapping")
default String fullNameMapping(String firstName) {
return firstName + " Smith";
}
}
5.3 其他
public class MappingContext {
// 上下文类中可以包含一些共享数据或方法
public String pContext(){
return "965";
}
}
5.4 测试用例
/**
* @BeanMapping:在 toDTO 和 updateEntityFromDTO 方法中使用,结合 ignoreByDefault = true 参数,用于忽略未显式映射的属性。这确保了只有明确映射的属性会被映射,从而避免了不必要的属性映射。
*
* @MappingTarget:在 updateEntityFromDTO 方法中使用,用于指示目标对象是现有对象,应在其基础上更新字段。
*
* @BeforeMapping 和 @AfterMapping:在映射前后执行额外操作。
*
* @Context:用于传递上下文信息,便于在映射过程中使用共享数据或方法。
*
* @InheritConfiguration 和 @InheritInverseConfiguration:用于继承其他映射方法的配置,以简化映射定义。
*
* @Named:定义自定义的映射方法。
*/
public class AnnotatinsTest {
@Test
public void testMapping() {
// 创建实体对象
UserEntity userEntity = new UserEntity();
userEntity.setId(1L);
userEntity.setFirstName("John");
userEntity.setLastName("Doe");
userEntity.setEmail("john.doe@example.com");
userEntity.setAddress("123 Main St");
// 创建上下文
MappingContext context = new MappingContext();
// 执行映射
UserDTO userDTO = UserMapper.INSTANCE.toDTO(userEntity, context);
System.out.println(userDTO.toString());
// 验证映射结果
assertNull(userDTO.getId());
assertEquals("John Smith", userDTO.getFullName());
assertEquals(userEntity.getEmail(), userDTO.getEmail());
// 确认忽略了 address 属性
}
@Test
public void testInverseMapping() {
// 创建 DTO 对象
UserDTO userDTO = new UserDTO();
userDTO.setId(1L);
userDTO.setFullName("John Smith");
userDTO.setEmail("john.smith@example.com");
// 创建上下文
MappingContext context = new MappingContext();
// 执行逆向映射
UserEntity userEntity = UserMapper.INSTANCE.toEntity(userDTO, context);
System.out.println(userEntity.toString());
// 验证逆向映射结果
assertNull(userEntity.getId());
assertEquals("John Smith Smith", userEntity.getFirstName());
assertNull(userEntity.getLastName());
assertEquals(userDTO.getEmail(), userEntity.getEmail());
}
@Test
public void testUpdateEntityFromDTO() {
// 创建初始实体对象
UserEntity userEntity = new UserEntity();
userEntity.setId(1L);
userEntity.setFirstName("John");
userEntity.setLastName("Doe");
userEntity.setEmail("john.doe@example.com");
userEntity.setAddress("123 Main St");
// 创建 DTO 对象
UserDTO userDTO = new UserDTO();
userDTO.setId(1L);
userDTO.setFullName("Sun Smith");
userDTO.setEmail("john.smith@example.com");
// 创建上下文
MappingContext context = new MappingContext();
// 执行更新映射
UserMapper.INSTANCE.updateEntityFromDTO(userDTO, userEntity, context);
System.out.println(userEntity.toString());
// 验证更新映射结果
assertEquals(userDTO.getId(), userEntity.getId());
assertEquals("Sun", userEntity.getFirstName());
assertEquals("Doe", userEntity.getLastName());
assertNotEquals(userDTO.getEmail(), userEntity.getEmail());
// 确认忽略了 address 属性
}
}
评论
匿名评论
隐私政策
你无需删除空行,直接评论以获取最佳展示效果