特性 | MapStruct | Orika |
---|---|---|
代码生成方式 | 编译时生成 Java 代码(无反射) | 运行时动态生成字节码(使用 Javassist/ASM) |
性能 | ⚡ 极高(直接调用生成的代码) | ⚡ 高(动态生成,但比反射快) |
启动时间 | ✅ 无影响(编译期完成) | ⏳ 首次运行时需生成映射代码 |
结论:
MapStruct 性能更高(适合高频调用场景)。
Orika 首次加载稍慢(但后续调用仍然很快)。
特性 | MapStruct | Orika |
---|---|---|
配置方式 | 注解驱动(@Mapper , @Mapping ) |
Fluent API 或 XML 配置 |
灵活性 | ✅ 适合简单映射,复杂逻辑需自定义方法 | 更灵活,支持动态条件映射 |
嵌套对象映射 | 支持(需显式配置) | 自动递归映射(默认支持) |
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();
// 订单实体(数据库模型)
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);
}
}
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 配置。
两者都是优秀的选择,根据项目需求权衡即可!