MapStruct动态生成实现
MapStruct 笔记
-
什么是 MapStruct?
MapStruct 是一个用于 Java 的代码生成器,它通过注解处理器在编译时生成实现类,用于对象之间的转换,特别是在数据传输对象(DTO)与实体之间的转换。它的主要优势在于性能高效、类型安全和易于维护。 -
主要特性
编译时生成代码: 避免了运行时反射的开销。
类型安全: 编译时检查类型,减少运行时错误。
支持复杂映射: 可以处理嵌套对象和集合。
自定义转换: 可以定义自定义方法来处理特殊的转换逻辑。
简化代码: 减少手动编写转换代码的工作量。 -
使用步骤
3.1 添加依赖
对于 Maven 项目,在 pom.xml 中添加以下依赖:
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct</artifactId><version>1.4.2.Final</version> <!-- 请根据最新版本进行调整 -->
</dependency>
<dependency><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>1.4.2.Final</version><scope>provided</scope>
</dependency>
对于 Gradle 项目,在 build.gradle 中添加:
implementation 'org.mapstruct:mapstruct:1.4.2.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
3.2 创建数据模型
根据你提供的接口定义,定义实体类和相关的 DTO:
// 实体类
public class AssetsAcceptItemsDetail {private Long id;private String name;private String description;// Getters and Setters
}
// 保存视图对象
public class AssetsAcceptItemsDetailSaveVo {private Long id;private String name;private String description;// Getters and Setters
}
// 其他相关类
public class AssetsVoForAcceptattach {private String assetName;private String assetDescription;// Getters and Setters
}
3.3 创建 Mapper 接口
定义 Mapper 接口,使用 @Mapper 注解,映射不同的转换方法:
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.List;
@Mapper
public interface AssetsAcceptItemsDetailCovert {AssetsAcceptItemsDetailCovert INSTANCE = Mappers.getMapper(AssetsAcceptItemsDetailCovert.class);AssetsAcceptItemsDetail clone(AssetsAcceptItemsDetail source);List<AssetsAcceptItemsDetail> clone(List<AssetsAcceptItemsDetail> sourceList);List<AssetsAcceptItemsDetail> saveVo2List(List<AssetsAcceptItemsDetailSaveVo> source);AssetsAcceptItemsDetail storeAssets2Model(AssetsVoForAcceptattach assetsVoForAcceptattach);
}
3.4 使用 Mapper
在你的服务或控制器中使用 Mapper:
public class AssetsService {private final AssetsAcceptItemsDetailCovert mapper = AssetsAcceptItemsDetailCovert.INSTANCE;public AssetsAcceptItemsDetail cloneDetail(AssetsAcceptItemsDetail detail) {return mapper.clone(detail);}public List<AssetsAcceptItemsDetail> cloneDetailList(List<AssetsAcceptItemsDetail> detailList) {return mapper.clone(detailList);}public List<AssetsAcceptItemsDetail> convertSaveVoList(List<AssetsAcceptItemsDetailSaveVo> saveVoList) {return mapper.saveVo2List(saveVoList);}public AssetsAcceptItemsDetail convertToModel(AssetsVoForAcceptattach assetsVo) {return mapper.storeAssets2Model(assetsVo);}
}
3.5 编译和生成代码
当你编译项目时,MapStruct 会自动生成实现类 AssetsAcceptItemsDetailCovertImpl,你可以在 target/generated-sources/annotations 目录下找到它。
- 复杂映射示例
如果你的对象有嵌套属性,MapStruct 也可以处理。例如,假设你有以下类:
public class AssetsAcceptItemsDetail {private Long id;private String name;private String description;private List<Asset> assets; // 假设有一个资产列表// Getters and Set
在使用 MapStruct 进行对象映射时,确实有不同的方式来处理字段的映射。你提到的两种方式各有优缺点,下面我们来详细讨论一下。
- 使用 @Mapping 注解的方式
@Mapping(source = "name", target = "fullName")
@Mapping(source = "description", target = "details")
AssetsAcceptItemsDetailVo toVo(AssetsAcceptItemsDetail detail);
优点
明确性: 每个字段的映射关系都非常清晰,便于理解和维护。
灵活性: 可以针对每个字段定义不同的映射规则,适用于复杂的转换需求。
自定义映射: 可以为特定字段提供自定义的转换逻辑。
缺点
冗长: 当字段较多时,代码会变得冗长,尤其是当源对象和目标对象有很多字段时。
维护成本: 如果字段发生变化(如添加、删除或重命名),需要在多个地方更新映射。
2. 直接使用 Mappers.getMapper()
AssetsAcceptItemsDetailCovert INSTANCE = Mappers.getMapper(AssetsAcceptItemsDetailCovert.class);
优点
简单性: 如果源对象和目标对象的字段名称相同,则不需要显式地定义每个字段的映射,减少了代码量。
易于维护: 当字段名称一致时,添加或删除字段时不需要更改映射逻辑,MapStruct 会自动处理。
缺点
灵活性不足: 如果字段名称不一致,或者需要进行复杂的转换逻辑,就需要使用 @Mapping 注解,可能会导致代码的重复。
可读性: 对于复杂的对象,直接使用 Mappers.getMapper() 可能会让人不易理解映射关系。
3. 何时选择哪种方式
字段名称一致且简单的情况:
如果源对象和目标对象的字段名称和类型完全一致,并且没有复杂的转换逻辑,使用 Mappers.getMapper() 是一个很好的选择。这种方式简单明了,易于维护。
字段名称不一致或需要复杂逻辑的情况:
如果字段名称不一致,或者需要自定义转换逻辑,使用 @Mapping 注解的方式将更加清晰和灵活。虽然代码会更长,但能够提供更好的可读性和可维护性。
4. 示例
简单映射示例(字段名称一致)
@Mapper
public interface AssetsAcceptItemsDetailCovert {AssetsAcceptItemsDetailCovert INSTANCE = Mappers.getMapper(AssetsAcceptItemsDetailCovert.class);AssetsAcceptItemsDetailVo toVo(AssetsAcceptItemsDetail detail);
}
复杂映射示例(字段名称不一致)
@Mapper
public interface AssetsAcceptItemsDetailCovert {AssetsAcceptItemsDetailCovert INSTANCE = Mappers.getMapper(AssetsAcceptItemsDetailCovert.class);@Mapping(source = "name", target = "fullName")@Mapping(source = "description", target = "details")AssetsAcceptItemsDetailVo toVo(AssetsAcceptItemsDetail detail);
}
- 结论
选择哪种方式取决于你的具体需求。如果你的对象映射比较简单且字段一致,使用 Mappers.getMapper() 是一个简洁的选择;如果字段不一致或者需要复杂的映射逻辑,使用 @Mapping 注解将更加灵活和可控。希望这些解释能帮助你更好地理解这两种方式的优缺点!如果你有更多问题,欢迎继续讨论。
MapStruct 笔记(修正版)
- 使用 @Mapping 注解
@Mapping 注解用于明确指定源对象和目标对象之间字段的映射关系。它的主要作用是处理字段名称不一致的情况。
1.1 特性
明确性: 可以清晰地定义每个字段的映射关系。
灵活性: 可以为字段提供自定义的转换逻辑。
必要性: 如果源和目标对象的字段名称一致,则不需要使用 @Mapping 注解。
1.2 示例
场景: 假设你有以下两个类,它们的字段名称不同:
public class AssetsAcceptItemsDetail {private Long id;private String name;private String description;// Getters and Setters
}
public class AssetsAcceptItemsDetailVo {private Long id;private String fullName; // 与 name 不同private String details; // 与 description 不同// Getters and Setters
}
Mapper 接口:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper
public interface AssetsAcceptItemsDetailCovert {@Mapping(source = "name", target = "fullName")@Mapping(source = "description", target = "details")AssetsAcceptItemsDetailVo toVo(AssetsAcceptItemsDetail detail);AssetsAcceptItemsDetail toDetail(AssetsAcceptItemsDetailVo detailVo);
}
1.3 说明
在这个例子中,@Mapping 注解用于指明 name 字段对应 fullName 字段,description 字段对应 details 字段。
如果字段名称相同(如 id),则不需要使用 @Mapping 注解。
2. 使用 Mappers.getMapper() 的方式
当源对象和目标对象的字段名称和类型一致时,可以直接使用 Mappers.getMapper() 方式来简化映射。
2.1 特性
简洁性: 如果字段名称和类型完全一致,代码会更加简洁。
自动处理: MapStruct 会自动处理字段名称一致的情况,无需显式定义。
2.2 示例
场景: 假设你有以下两个类,它们的字段名称和类型一致:
public class User {private Long id;private String name;private String email;// Getters and Setters
}
public class UserDTO {private Long id;private String name; // 与 User 的 name 相同private String email; // 与 User 的 email 相同// Getters and Setters
}
Mapper 接口:
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);UserDTO userToUser DTO(User user);User userDTOToUser (UserDTO userDTO);
}
2.3 说明
在这个例子中,User 和 User DTO 的字段名称和类型一致,因此不需要使用 @Mapping 注解。
通过 Mappers.getMapper(UserMapper.class) 获取 User Mapper 的实例,自动处理字段映射。
2.4 使用 Mapper
在服务或控制器中使用 Mapper 实例:
public class UserService {public UserDTO convertToDTO(User user) {return UserMapper.INSTANCE.userToUser DTO(user);}public User convertToEntity(UserDTO userDTO) {return UserMapper.INSTANCE.userDTOToUser (userDTO);}
}
- 总结
使用 @Mapping 注解的方式
优点: 明确、灵活,适合字段名称不一致的情况。
示例代码:
@Mapping(source = "name", target = "fullName")
@Mapping(source = "description", target = "details")
AssetsAcceptItemsDetailVo toVo(AssetsAcceptItemsDetail detail);
使用 Mappers.getMapper() 的方式
优点: 简洁、自动处理,适合字段名称一致的情况。
示例代码:
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserDTO userToUser DTO(User user);
结论
选择使用哪种方式取决于你的具体需求。如果你有很多字段需要映射,并且它们的名称和类型一致,使用 Mappers.getMapper() 是一个更简洁的选择;如果字段名称不一致或者需要复杂的映射逻辑,使用 @Mapping 注解将更加灵活和可控。