java Bean映射转换库 MapStruct
在java项目中每次开发,对于数据库对象每次使用都需要根据前端得要求频繁得转换vo,虽然有工具类库比如beanUtils,但是对于BeanUtils来说性能更好
MapStruct 和 BeanUtils 都是用于对象映射的工具,但它们在实现方式、性能、灵活性和适用场景上有显著区别。以下是详细的对比:
对比传统的BeanUtils
MapStruct
- 编译时生成代码:MapStruct 是一个代码生成工具,它在编译时根据映射接口生成具体的映射实现类。
- 类型安全:由于映射逻辑是编译时生成的,因此编译器可以检查类型是否匹配,避免运行时错误。
- 显式映射:需要显式定义映射规则(通过
@Mapper
和@Mapping
注解)。
BeanUtils
- 运行时反射:BeanUtils 使用 Java 反射机制在运行时动态复制对象的属性。
- 隐式映射:默认情况下,BeanUtils 会根据字段名自动匹配并复制属性,无需显式定义映射规则。
2. 性能
MapStruct
- 高性能:由于映射逻辑是编译时生成的,生成的代码是普通的 Java 方法调用,没有反射开销,性能接近手写代码。
- 适合高频调用:在需要频繁进行对象映射的场景下(如 Web 请求处理),MapStruct 的性能优势非常明显。
BeanUtils
- 性能较低:BeanUtils 依赖反射机制,反射操作在运行时会有额外的性能开销。
- 适合低频调用:在不需要频繁映射的场景下,性能问题可能不明显,但在高频调用时性能瓶颈会显现。
3. 灵活性
MapStruct
- 高度灵活:支持复杂的映射规则,例如:
- 字段名不一致的映射(通过
@Mapping
注解)。 - 自定义类型转换(通过
@AfterMapping
或自定义方法)。 - 多对象映射(从多个源对象映射到一个目标对象)。
- 字段名不一致的映射(通过
- 编译时检查:如果映射规则有误(如字段名拼写错误),会在编译时报错,避免运行时问题。
BeanUtils
- 灵活性有限:默认情况下,BeanUtils 只能根据字段名自动复制属性,无法处理字段名不一致或复杂转换逻辑。
- 运行时错误:如果字段名拼写错误或类型不匹配,只能在运行时发现,可能导致
NullPointerException
或其他异常。
4. 适用场景
MapStruct
- 适合复杂映射:当需要处理复杂的映射逻辑(如字段名不一致、类型转换、多对象映射)时,MapStruct 是更好的选择。
- 高性能需求:在需要高频调用映射逻辑的场景下(如 Web 请求处理),MapStruct 的性能优势非常明显。
- 类型安全需求:在需要确保映射逻辑类型安全的场景下,MapStruct 是更好的选择。
BeanUtils
- 适合简单映射:当映射逻辑非常简单(字段名一致,无需类型转换)时,BeanUtils 可以快速实现映射。
- 低频调用场景:在不需要频繁调用映射逻辑的场景下,BeanUtils 的性能问题可能不明显。
- 快速原型开发:在快速原型开发中,BeanUtils 可以节省时间,无需定义映射规则。
5. 代码示例对比
MapStruct 示例
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
@Mapping(source = "email", target = "emailAddress")
@Mapping(target = "password", ignore = true)
UserVO toUserVO(User user);
}
// 使用
UserVO userVO = UserMapper.INSTANCE.toUserVO(user);
BeanUtils 示例
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO); // 自动复制属性
userVO.setPassword(null); // 需要手动处理敏感字段
6. 总结对比
特性 | MapStruct | BeanUtils |
---|---|---|
实现方式 | 编译时生成代码 | 运行时反射 |
性能 | 高性能,接近手写代码 | 性能较低,反射开销较大 |
灵活性 | 高度灵活,支持复杂映射规则 | 灵活性有限,仅支持简单映射 |
类型安全 | 编译时检查,类型安全 | 运行时检查,可能抛出异常 |
适用场景 | 复杂映射、高性能需求、类型安全需求 | 简单映射、低频调用、快速开发 |
使用教程
依赖包
<dependencies>
<!-- MapStruct 核心库 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.5.Final</version>
</dependency>
<!-- MapStruct 注解处理器 -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.5.Final</version>
<scope>provided</scope>
</dependency>
</dependencies>
实体类和试图对象转换
数据库实体类
@Data
@TableName("appointment")
@Builder
public class AppointmentEntity {
/**
* 预约id
*/
@TableId
@TableField(value = "id")
@Schema(description="当前数据id") //Spingdoc注解
private Long id;
/**
* 租户
*/
@TableField(value = "tenant_id")
@Schema(description="租户id")
private Long tenantId;
/**
* 客户id
客户id
*/
@Schema(description="客户id")
@TableField(value = "customer_id")
private Long customerId;
/**
* 该预约的员工id
*/
@Schema(description="客户预约的员工id")
@TableField(value = "employee_id")
private Long employeeId;
/**
* 服务类型
*/
@Schema(description="预约服务类型")
@TableField(value = "service_type")
private String serviceType;
/**
* 预约时间
*/
@Schema(description="预约时间")
@TableField(value = "appointment_time")
private Date appointmentTime;
/**
* '01','02','03'
*/
@Schema(description="'01','02','03' 代表预约状态")
@TableField(value = "status")
private String status;
/**
* 创建时间
*/
@Schema(description="创建时间")
@TableField(value = "created_at")
private Date createdAt;
@Schema(description="修改时间")
@TableField(value = "updated_at")
private Date updatedAt;
}
VO对象 这里我认为创建时间不应该返回给前端,排序后端直接就排序好了 前端不需要知道时间信息
@Data
@Schema(description = "预约事件表")
public class AppointmentVO {
@Schema(description = "预约数据id")
private Long id;
@Schema(description = "租户")
private Long tenantId;
@Schema(description = "客户id ")
private Long customerId;
@Schema(description = "该预约的员工id")
private Long employeeId;
@Schema(description = "服务类型")
private String serviceType;
@Schema(description = "预约时间")
@JsonFormat(pattern = DateUtils.DATE_TIME_PATTERN)
private Date appointmentTime;
@Schema(description = "'01','02','03'")
private String status;
}
写一个转换器接口
//注意别和mybatis的mapper包导入错了
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
/**
* 附件表转换器
*/
@Mapper
public interface AppointmentConvert {
AppointmentConvert INSTANCE = Mappers.getMapper(AppointmentConvert.class);
AppointmentEntity convert(AppointmentVO vo);
AppointmentVO convert(AppointmentEntity entity);
//实体转换为vo 批量
List<AppointmentVO> convertList(List<AppointmentEntity> list);
//vo转换为实体 批量
List<AppointmentEntity> convertList2(List<AppointmentVO> list);
}
使用
测试代码:
@Slf4j
@SpringBootTest(classes = SalonApplication.class)
public class MapStructTest {
@DisplayName("Test mapstruct")
@Test
public void testMapstruct() {
//模拟数据库查出来的实体类列表
ArrayList<AppointmentEntity> entities = new ArrayList<>();
for (int i = 0; i < 10; i++) {
entities.add(AppointmentEntity.builder()
.id((long) i)
.customerId((long) i+5)
.employeeId((long) i+10)
.serviceType("serviceType"+i)
.appointmentTime(new Date())
.status("01")
.createdAt(new Date())
.updatedAt(new Date())
.build()
);
}
//测试转
AppointmentConvert.INSTANCE.convertList(entities).forEach(System.out::println);
}
}
输出
可以发现这个实现类是编译后,mapstruct自动生成的
如果又需要赋值了,有些属性不需要赋值 那么需要情理当前maven 编译结果重新在运行 编译一次
这个还是很好用的,一旦数据量大了 性能返回相应时间还是比beanutils快很多
@DisplayName("Test mapstruct vs BeanUtils 时间对比")
@Test
public void testMapstructVsBeanUtils() {
// 模拟数据库查出来的实体类列表
ArrayList<AppointmentEntity> entities = new ArrayList<>();
// 生成 1000 个 AppointmentEntity 对象
for (int i = 0; i < 1000; i++) {
entities.add(AppointmentEntity.builder()
.id((long) i)
.customerId((long) i + 5)
.employeeId((long) i + 10)
.serviceType("serviceType" + i)
.appointmentTime(new Date())
.status("01")
.createdAt(new Date())
.updatedAt(new Date())
.build()
);
}
// 测试 MapStruct 转换
long startMapStruct = System.nanoTime();
// AppointmentConvert.INSTANCE.convertList(entities).forEach(System.out::println);
long endMapStruct = System.nanoTime();
long mapStructTime = endMapStruct - startMapStruct;
// 测试 BeanUtils 转换
long startBeanUtils = System.nanoTime();
ArrayList<AppointmentVO> beanUtilsResult = new ArrayList<>();
entities.forEach(entity -> {
AppointmentVO vo = new AppointmentVO();
BeanUtils.copyProperties(entity, vo);
beanUtilsResult.add(vo);
});
// beanUtilsResult.forEach(System.out::println);
long endBeanUtils = System.nanoTime();
long beanUtilsTime = endBeanUtils - startBeanUtils;
// 打印性能对比结果
System.out.println("MapStruct 转换时间: " + mapStructTime + " 纳秒");
System.out.println("BeanUtils 转换时间: " + beanUtilsTime + " 纳秒");
System.out.println("MapStruct 比 BeanUtils 快: " + (beanUtilsTime - mapStructTime) + " 纳秒");
}
输出结果
这还只是1000条数据的转换