当前位置: 首页 > article >正文

java Bean映射转换库 ​MapStruct​

在java项目中每次开发,对于数据库对象每次使用都需要根据前端得要求频繁得转换vo,虽然有工具类库比如beanUtils,但是对于BeanUtils来说性能更好

MapStructBeanUtils 都是用于对象映射的工具,但它们在实现方式、性能、灵活性和适用场景上有显著区别。以下是详细的对比:


对比传统的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. 总结对比

特性MapStructBeanUtils
实现方式编译时生成代码运行时反射
性能高性能,接近手写代码性能较低,反射开销较大
灵活性高度灵活,支持复杂映射规则灵活性有限,仅支持简单映射
类型安全编译时检查,类型安全运行时检查,可能抛出异常
适用场景复杂映射、高性能需求、类型安全需求简单映射、低频调用、快速开发

使用教程

依赖包


<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条数据的转换


http://www.kler.cn/a/567615.html

相关文章:

  • c++---二叉搜索树
  • LLM的演进趋势与未来展望:Toformer的革新之路
  • 如何杀死僵尸进程?没有那个进程?
  • Mixture of Experts与Meta Learning深度学习中的两大变革性技术
  • Text-to-SQL将自然语言转换为数据库查询语句
  • pyside6学习专栏(八):在PySide6中使用matplotlib库绘制三维图形
  • Swan 表达式 - 选择表达式
  • 【由技及道】模块化战争与和平-论项目结构的哲学思辨【人工智智障AI2077的开发日志】
  • 美团自动驾驶决策规划算法岗内推
  • 将QT移植到RK3568开发板
  • 酒店管理系统(代码+数据库+LW)
  • MySQL并发知识(面试高频)
  • SOLID Principle基础入门
  • 机器学习3-聚类
  • 【图像平移、旋转、仿射变换、投影变换】
  • threeJs+vue 轻松切换几何体贴图
  • Flutter 学习之旅 之 flutter 使用 fluttertoast 的 toast 实现简单的 Toast 效果
  • 基于单片机的智能扫地机器人
  • ArcGIS Pro高级技巧:高效填充DEM数据空洞
  • 软件测试中的BUG