MapStruct 与 Orika的使用区别

MapStruct 与 Orika的使用区别

1. 代码生成方式

特性 MapStruct Orika
代码生成方式 编译时生成 Java 代码(无反射) 运行时动态生成字节码(使用 Javassist/ASM)
性能 ⚡ 极高(直接调用生成的代码) ⚡ (动态生成,但比反射快)
启动时间 ✅ 无影响(编译期完成) ⏳ 首次运行时需生成映射代码

结论

  • MapStruct 性能更高(适合高频调用场景)。

  • Orika 首次加载稍慢(但后续调用仍然很快)。


2. 映射配置方式

特性 MapStruct Orika
配置方式 注解驱动@Mapper@Mapping Fluent API 或 XML 配置
灵活性 ✅ 适合简单映射,复杂逻辑需自定义方法 更灵活,支持动态条件映射
嵌套对象映射 支持(需显式配置) 自动递归映射(默认支持)

3.简单使用案例

mapstruct案例

java
@Mapper
public interface CarMapper {
@Mapping(source = “make”, target = “brand”)
CarDTO toDto(Car car);
}


### Orika使用案例

```java
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
mapperFactory.classMap(Car.class, CarDTO.class)
    .field("make", "brand")
    .byDefault()
    .register();

4.项目实现案例记录

mapstruct

// 订单实体(数据库模型)
public class Order {
    private Long id;
    private String orderNo;
    private BigDecimal amount;
    private Date createTime;
    private Customer customer;  // 嵌套对象
    // getters/setters...
}

// 订单DTO(前端返回模型)
public class OrderDTO {
    private Long orderId;       // 字段名不同
    private String serialNumber; // 字段名不同
    private String totalAmount; // 类型不同(BigDecimal → String)
    private String createTime;  // 类型不同(Date → String)
    private String customerName; // 嵌套字段扁平化
    // getters/setters...
}

// 客户实体(嵌套对象)
public class Customer {
    private String name;
    // getters/setters...
}

定义mapper映射接口,用 @Mapper 标记接口,通过 @Mapping 指定字段映射规则

import org.mapstruct.*;
import java.text.SimpleDateFormat;

@Mapper(uses = DateMapper.class) // 使用自定义转换器
public interface OrderMapper {

    OrderMapper INSTANCE = Mappers.getMapper(OrderMapper.class);

    // 字段映射:source→target
    @Mapping(source = "id", target = "orderId")
    @Mapping(source = "orderNo", target = "serialNumber")
    @Mapping(source = "amount", target = "totalAmount")
    @Mapping(source = "createTime", target = "createTime")
    @Mapping(source = "customer.name", target = "customerName") // 嵌套对象映射
    OrderDTO toDTO(Order order);

    // 反向映射(可选)
    @InheritInverseConfiguration
    Order toEntity(OrderDTO dto);
}

自定义类型转换器(date->string)

public class DateMapper {
    public String dateToString(Date date) {
        return date != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date) : null;
    }

    public Date stringToDate(String dateStr) {
        try {
            return dateStr != null ? new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateStr) : null;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Order order = new Order();
        order.setId(1L);
        order.setOrderNo("NO20231101");
        order.setAmount(new BigDecimal("99.99"));
        order.setCreateTime(new Date());
        order.setCustomer(new Customer("张三"));

        // 转换:Order → OrderDTO
        OrderDTO dto = OrderMapper.INSTANCE.toDTO(order);
        System.out.println(dto.getSerialNumber()); // 输出: NO20231101
        System.out.println(dto.getCustomerName()); // 输出: 张三

        // 反向转换:OrderDTO → Order
        Order entity = OrderMapper.INSTANCE.toEntity(dto);
    }
}

Orika 实现案例

package com.itheima.sfbx.framework.commons.utils;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.MappingContext;
import ma.glasnost.orika.converter.BidirectionalConverter;
import ma.glasnost.orika.converter.ConverterFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import ma.glasnost.orika.metadata.ClassMapBuilder;
import ma.glasnost.orika.metadata.Type;
import org.springframework.beans.BeanUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.List;

/***
 * @description 对象转换工具,当对象成员变量属性:名称及类型相同时候会自动
 * 填充其值
 *
 */
@Slf4j
public class BeanConv {

    private static MapperFacade mapper;

    private static MapperFacade notNullMapper;

    static {
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build();
        ConverterFactory converterFactory = mapperFactory.getConverterFactory();
        converterFactory.registerConverter(new LocalDateTimeConverter());
        converterFactory.registerConverter(new LocalDateConverter());
        converterFactory.registerConverter(new LocalTimeConverter());
        mapper = mapperFactory.getMapperFacade();
        MapperFactory notNullMapperFactory = new DefaultMapperFactory.Builder().mapNulls(false).build();
        notNullMapper = notNullMapperFactory.getMapperFacade();
    }

    private static class LocalDateTimeConverter extends BidirectionalConverter<LocalDateTime, LocalDateTime> {

        @Override
        public LocalDateTime convertTo(LocalDateTime localDateTime, Type<LocalDateTime> type, MappingContext mappingContext) {
            return LocalDateTime.from(localDateTime);
        }

        @Override
        public LocalDateTime convertFrom(LocalDateTime localDateTime, Type<LocalDateTime> type, MappingContext mappingContext) {
            return LocalDateTime.from(localDateTime);
        }
    }
    private static class LocalDateConverter extends BidirectionalConverter<LocalDate, LocalDate> {
        @Override
        public LocalDate convertTo(LocalDate localDate, Type<LocalDate> type, MappingContext mappingContext) {
            return LocalDate.from(localDate);
        }

        @Override
        public LocalDate convertFrom(LocalDate localDate, Type<LocalDate> type, MappingContext mappingContext) {
            return LocalDate.from(localDate);
        }
    }
    private static class LocalTimeConverter extends BidirectionalConverter<LocalTime, LocalTime> {

        @Override
        public LocalTime convertTo(LocalTime localTime, Type<LocalTime> type, MappingContext mappingContext) {
            return LocalTime.from(localTime);
        }

        @Override
        public LocalTime convertFrom(LocalTime localTime, Type<LocalTime> type, MappingContext mappingContext) {
            return LocalTime.from(localTime);
        }
    }


    /**
     * 分页对象复制
     * @param source      源对象
     * @param destinationClass 目标对象类型
     */
    public static <S,D> Page<D> toPage(Page<S> source, Class<D> destinationClass) {
        if (EmptyUtil.isNullOrEmpty(source)){
            return null;
        }
        Class<? extends Page> handlerClass = source.getClass();
        Page<D> destination = mapper.map(source, handlerClass);
        destination.setRecords(mapper.mapAsList(source.getRecords(),destinationClass));
        return destination;
    }

    /***
     * @description 深度复制对象
     *
     * @param source 源对象
     * @param destinationClass 目标类型
     * @return
     */
    public static <T> T toBean(Object source, Class<T> destinationClass) {
        if (EmptyUtil.isNullOrEmpty(source)){
            return null;
        }
        return mapper.map(source, destinationClass);
    }

    /***
     * @description 深度复制对象
     *
     * @param source 源对象
     * @param destinationClass 目标类型
     * @return
     */
    public static <T> T toBean(Object source, Class<T> destinationClass, String... fieldsToIgnore) {
        try {
            T t = destinationClass.getDeclaredConstructor().newInstance();
            BeanUtils.copyProperties(source, t, fieldsToIgnore);
            return t;
        }catch (Exception e){
            ExceptionsUtil.getStackTraceAsString(e);
            return null;
        }
    }


    /***
     * @description 复制List
     *
     * @param sourceList 源list对象
     * @param destinationClass 目标类型
     * @return
     */
    public static <T> List<T> toBeanList(List<?> sourceList, Class<T> destinationClass) {
        if (EmptyUtil.isNullOrEmpty(sourceList)){
            return new ArrayList<>();
        }
        return mapper.mapAsList(sourceList,destinationClass);
    }

}

 //转换SafeguardVO为Safeguard
 Safeguard safeguard = BeanConv.toBean(safeguardVO, Safeguard.class);
public class LocalDateTimeToStringConverter extends BidirectionalConverter<LocalDateTime, String> {
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

    @Override
    public String convertTo(LocalDateTime source, Type<String> targetType, MappingContext context) {
        return source.format(FORMATTER); // LocalDateTime → String
    }

    @Override
    public LocalDateTime convertFrom(String source, Type<LocalDateTime> targetType, MappingContext context) {
        return LocalDateTime.parse(source, FORMATTER); // String → LocalDateTime
    }
}

上面是date类型和String类型的互换。

总结


复杂映射支持

特性 MapStruct Orika
自定义转换逻辑 支持(@Named 或 default 方法) 支持(Converter 接口)
集合映射 自动支持 List → List 自动支持,并可自定义元素转换
深拷贝 需手动配置 默认递归深拷贝
条件映射 需自定义方法 支持 filter 条件过滤

结论

  • Orika 更适合复杂、动态的映射场景(如深拷贝、条件过滤)。

  • MapStruct 适合简单、高性能的映射(代码可读性更强)。


依赖和集成

特性 MapStruct Orika
依赖库 仅需 mapstruct + 注解处理器 依赖 orika-core(含 Javassist)
Spring 集成 支持(componentModel = "spring" 支持(可注册为 Bean)
CDI/JSR-330 支持 支持

结论

  • MapStruct 更轻量(无额外运行时依赖)。

  • Orika 功能更全(但依赖字节码操作库)。

适用场景总结

场景 推荐框架 原因
高性能、编译期检查 MapStruct 无反射,直接生成代码
复杂、动态映射 Orika Fluent API 更灵活
Spring 项目 MapStruct 注解驱动,集成简单
需要深拷贝 Orika 默认递归映射
微服务/DTO转换 MapStruct 类型安全,代码可维护

最终选择建议

  • 选 MapStruct

    • 追求 极致性能(如高频调用的核心服务)。

    • 需要 编译时类型安全(避免运行时错误)。

    • 项目结构简单,映射逻辑固定。

  • 选 Orika

    • 需要 动态、复杂映射(如条件过滤、深拷贝)。

    • 不介意 轻微的性能损耗(仍比反射快)。

    • 习惯 Fluent API 或 XML 配置。

两者都是优秀的选择,根据项目需求权衡即可!

你可能感兴趣的:(java,后端)