Atlas Mapper 教程系列 (4/10):高级映射技巧与类型转换

🎯 学习目标

通过本篇教程,你将学会:

  • 掌握复杂类型转换的实现方法
  • 学会创建和使用自定义类型转换器
  • 理解表达式映射的高级用法
  • 掌握条件映射和动态映射技巧

📋 概念讲解:高级映射机制

类型转换层次结构

graph TB subgraph Built["内置转换器"] A1[基础类型转换] --> A2["String ↔ Number"] A1 --> A3["String ↔ Boolean"] A1 --> A4["String ↔ Date"] A1 --> A5["Number ↔ Number"] end subgraph Format["格式化转换"] B1[日期格式化] --> B2[dateFormat] B3[数字格式化] --> B4[numberFormat] B5[自定义格式] --> B6[pattern] end subgraph Custom["自定义转换器"] C1["@Mapper.uses"] --> C2[引用转换器类] C3["@Named"] --> C4[命名转换方法] C5[qualifiedByName] --> C6[指定转换方法] end subgraph Expression["表达式映射"] D1[Java表达式] --> D2[expression] D3[常量表达式] --> D4[constant] D5[默认值] --> D6[defaultValue] end A1 --> B1 B1 --> C1 C1 --> D1 style A1 fill:#e8f5e8 style C1 fill:#e3f2fd style D1 fill:#fff3e0

映射优先级和决策流程

flowchart TD A[开始字段映射] --> B{"是否有@Mapping?"} B -->|有| C{"是否有expression?"} B -->|无| D{"字段名相同?"} C -->|有| E[执行Java表达式] C -->|无| F{"是否有constant?"} F -->|有| G[使用常量值] F -->|无| H{"是否有qualifiedByName?"} H -->|有| I[调用自定义方法] H -->|无| J{"是否有格式化?"} J -->|有| K[应用格式化转换] J -->|无| L[使用内置转换器] D -->|相同| M{"类型兼容?"} D -->|不同| N[跳过映射] M -->|兼容| O[直接赋值] M -->|不兼容| P[查找类型转换器] E --> Q[完成映射] G --> Q I --> Q K --> Q L --> Q O --> Q P --> Q N --> Q style E fill:#c8e6c9 style I fill:#e8f5e8 style K fill:#fff3e0 style P fill:#ffcdd2

🔧 实现步骤:高级映射技巧详解

步骤 1:内置类型转换器

Atlas Mapper 提供了丰富的内置类型转换器:

java 复制代码
/**
 * 内置类型转换示例
 */
public class ConversionExample {
    
    // 源实体 - 各种类型的字段
    public static class SourceEntity {
        private String numberStr = "123";           // String -> Number
        private String booleanStr = "true";         // String -> Boolean  
        private String dateStr = "2025-01-09";      // String -> Date
        private Integer intValue = 456;             // Integer -> Long
        private Long longValue = 789L;              // Long -> String
        private Boolean boolValue = true;           // Boolean -> String
        private LocalDateTime dateTime = LocalDateTime.now(); // LocalDateTime -> String
        
        // getter/setter...
    }
    
    // 目标 DTO - 转换后的类型
    public static class TargetDto {
        private Integer numberValue;                // String -> Integer
        private Boolean booleanValue;               // String -> Boolean
        private LocalDate dateValue;                // String -> LocalDate
        private Long longValue;                     // Integer -> Long
        private String longStr;                     // Long -> String
        private String boolStr;                     // Boolean -> String
        private String dateTimeStr;                 // LocalDateTime -> String
        
        // getter/setter...
    }
}

@Mapper(componentModel = "spring")
public interface ConversionMapper {
    
    /**
     * 内置转换器自动处理类型转换
     */
    @Mapping(target = "numberValue", source = "numberStr")      // String -> Integer
    @Mapping(target = "booleanValue", source = "booleanStr")    // String -> Boolean
    @Mapping(target = "dateValue", source = "dateStr", 
             dateFormat = "yyyy-MM-dd")                         // String -> LocalDate
    @Mapping(target = "longValue", source = "intValue")         // Integer -> Long
    @Mapping(target = "longStr", source = "longValue")          // Long -> String
    @Mapping(target = "boolStr", source = "boolValue")          // Boolean -> String
    @Mapping(target = "dateTimeStr", source = "dateTime", 
             dateFormat = "yyyy-MM-dd HH:mm:ss")                // LocalDateTime -> String
    TargetDto convert(SourceEntity source);
}

步骤 2:自定义类型转换器

创建专门的转换器类来处理复杂的类型转换:

java 复制代码
/**
 * 自定义类型转换器类
 */
@Component
public class CustomTypeConverter {
    
    /**
     * 枚举转换器:状态码 -> 状态描述
     */
    @Named("statusCodeToDescription")
    public String statusCodeToDescription(Integer statusCode) {
        if (statusCode == null) {
            return "未知状态";
        }
        
        switch (statusCode) {
            case 0: return "待处理";
            case 1: return "处理中";
            case 2: return "已完成";
            case 3: return "已取消";
            default: return "未知状态";
        }
    }
    
    /**
     * 反向转换:状态描述 -> 状态码
     */
    @Named("descriptionToStatusCode")
    public Integer descriptionToStatusCode(String description) {
        if (description == null) {
            return 0;
        }
        
        switch (description) {
            case "待处理": return 0;
            case "处理中": return 1;
            case "已完成": return 2;
            case "已取消": return 3;
            default: return 0;
        }
    }
    
    /**
     * 复杂对象转换:地址对象 -> 地址字符串
     */
    @Named("addressToString")
    public String addressToString(Address address) {
        if (address == null) {
            return "";
        }
        
        StringBuilder sb = new StringBuilder();
        if (address.getProvince() != null) {
            sb.append(address.getProvince());
        }
        if (address.getCity() != null) {
            sb.append(address.getCity());
        }
        if (address.getDistrict() != null) {
            sb.append(address.getDistrict());
        }
        if (address.getDetail() != null) {
            sb.append(address.getDetail());
        }
        
        return sb.toString();
    }
    
    /**
     * 金额转换:分 -> 元(格式化)
     */
    @Named("centToYuan")
    public String centToYuan(Long centAmount) {
        if (centAmount == null) {
            return "¥0.00";
        }
        
        BigDecimal yuan = new BigDecimal(centAmount).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
        return "¥" + yuan.toString();
    }
    
    /**
     * 时间戳转换:Long -> 相对时间描述
     */
    @Named("timestampToRelativeTime")
    public String timestampToRelativeTime(Long timestamp) {
        if (timestamp == null) {
            return "未知时间";
        }
        
        LocalDateTime dateTime = LocalDateTime.ofInstant(
            Instant.ofEpochMilli(timestamp), 
            ZoneId.systemDefault()
        );
        LocalDateTime now = LocalDateTime.now();
        
        Duration duration = Duration.between(dateTime, now);
        long days = duration.toDays();
        long hours = duration.toHours();
        long minutes = duration.toMinutes();
        
        if (days > 0) {
            return days + "天前";
        } else if (hours > 0) {
            return hours + "小时前";
        } else if (minutes > 0) {
            return minutes + "分钟前";
        } else {
            return "刚刚";
        }
    }
}

步骤 3:使用自定义转换器

在映射器中引用和使用自定义转换器:

java 复制代码
// 任务实体类
public class Task {
    private Long id;
    private String title;
    private String description;
    private Integer status;           // 状态码:0,1,2,3
    private Long amountInCent;        // 金额(分)
    private Address address;          // 地址对象
    private Long createdTimestamp;    // 创建时间戳
    private Long updatedTimestamp;    // 更新时间戳
    
    // getter/setter...
}

// 任务 DTO 类
public class TaskDto {
    private Long id;
    private String title;
    private String description;
    private String statusDesc;        // 状态描述
    private String amount;            // 格式化金额
    private String fullAddress;       // 完整地址字符串
    private String createdTime;       // 相对创建时间
    private String updatedTime;       // 相对更新时间
    
    // getter/setter...
}

// 地址实体类
public class Address {
    private String province;
    private String city;
    private String district;
    private String detail;
    
    // getter/setter...
}

/**
 * 使用自定义转换器的映射器
 */
@Mapper(
    componentModel = "spring",
    uses = CustomTypeConverter.class    // 🔥 引用自定义转换器类
)
public interface TaskMapper {
    
    @Mapping(target = "statusDesc", source = "status", 
             qualifiedByName = "statusCodeToDescription")           // 🔥 使用命名转换方法
    @Mapping(target = "amount", source = "amountInCent", 
             qualifiedByName = "centToYuan")                        // 🔥 金额转换
    @Mapping(target = "fullAddress", source = "address", 
             qualifiedByName = "addressToString")                   // 🔥 地址转换
    @Mapping(target = "createdTime", source = "createdTimestamp", 
             qualifiedByName = "timestampToRelativeTime")           // 🔥 时间转换
    @Mapping(target = "updatedTime", source = "updatedTimestamp", 
             qualifiedByName = "timestampToRelativeTime")           // 🔥 时间转换
    TaskDto toDto(Task task);
    
    /**
     * 反向映射:DTO -> Entity
     */
    @Mapping(target = "status", source = "statusDesc", 
             qualifiedByName = "descriptionToStatusCode")           // 🔥 反向状态转换
    @Mapping(target = "amountInCent", ignore = true)                // 🔥 忽略复杂转换
    @Mapping(target = "address", ignore = true)                     // 🔥 忽略复杂对象
    @Mapping(target = "createdTimestamp", ignore = true)            // 🔥 忽略时间戳
    @Mapping(target = "updatedTimestamp", ignore = true)            // 🔥 忽略时间戳
    Task toEntity(TaskDto dto);
}

步骤 4:表达式映射高级用法

使用 Java 表达式实现复杂的映射逻辑:

java 复制代码
// 用户实体类
public class User {
    private String firstName;
    private String lastName;
    private String email;
    private LocalDate birthDate;
    private String phone;
    private Integer gender;           // 0:未知, 1:男, 2:女
    private List<String> hobbies;
    private BigDecimal salary;
    private Boolean isVip;
    
    // getter/setter...
}

// 用户详情 DTO
public class UserDetailDto {
    private String fullName;          // 组合姓名
    private String email;
    private Integer age;              // 计算年龄
    private String genderDesc;        // 性别描述
    private String phoneFormatted;    // 格式化手机号
    private String hobbiesText;       // 爱好文本
    private String salaryLevel;       // 薪资等级
    private String membershipType;    // 会员类型
    private String profileSummary;    // 个人简介
    
    // getter/setter...
}

@Mapper(componentModel = "spring")
public interface UserDetailMapper {
    
    /**
     * 使用表达式映射实现复杂逻辑
     */
    @Mapping(target = "fullName", 
             expression = "java(user.getFirstName() + \" \" + user.getLastName())")
    
    @Mapping(target = "age", 
             expression = "java(java.time.Period.between(user.getBirthDate(), java.time.LocalDate.now()).getYears())")
    
    @Mapping(target = "genderDesc", 
             expression = "java(mapGender(user.getGender()))")
    
    @Mapping(target = "phoneFormatted", 
             expression = "java(formatPhone(user.getPhone()))")
    
    @Mapping(target = "hobbiesText", 
             expression = "java(user.getHobbies() != null ? String.join(\", \", user.getHobbies()) : \"无\")")
    
    @Mapping(target = "salaryLevel", 
             expression = "java(calculateSalaryLevel(user.getSalary()))")
    
    @Mapping(target = "membershipType", 
             expression = "java(user.getIsVip() != null && user.getIsVip() ? \"VIP会员\" : \"普通会员\")")
    
    @Mapping(target = "profileSummary", 
             expression = "java(generateProfileSummary(user))")
    
    UserDetailDto toDetailDto(User user);
    
    /**
     * 辅助方法:性别映射
     */
    default String mapGender(Integer gender) {
        if (gender == null) return "未知";
        switch (gender) {
            case 1: return "男";
            case 2: return "女";
            default: return "未知";
        }
    }
    
    /**
     * 辅助方法:手机号格式化
     */
    default String formatPhone(String phone) {
        if (phone == null || phone.length() != 11) {
            return phone;
        }
        return phone.substring(0, 3) + "****" + phone.substring(7);
    }
    
    /**
     * 辅助方法:薪资等级计算
     */
    default String calculateSalaryLevel(BigDecimal salary) {
        if (salary == null) {
            return "未知";
        }
        
        if (salary.compareTo(new BigDecimal("5000")) < 0) {
            return "初级";
        } else if (salary.compareTo(new BigDecimal("10000")) < 0) {
            return "中级";
        } else if (salary.compareTo(new BigDecimal("20000")) < 0) {
            return "高级";
        } else {
            return "专家级";
        }
    }
    
    /**
     * 辅助方法:生成个人简介
     */
    default String generateProfileSummary(User user) {
        StringBuilder summary = new StringBuilder();
        
        // 基本信息
        summary.append(user.getFirstName()).append(" ").append(user.getLastName());
        
        // 年龄
        if (user.getBirthDate() != null) {
            int age = Period.between(user.getBirthDate(), LocalDate.now()).getYears();
            summary.append(",").append(age).append("岁");
        }
        
        // 性别
        summary.append(",").append(mapGender(user.getGender()));
        
        // VIP状态
        if (user.getIsVip() != null && user.getIsVip()) {
            summary.append(",VIP会员");
        }
        
        // 爱好
        if (user.getHobbies() != null && !user.getHobbies().isEmpty()) {
            summary.append(",爱好:").append(String.join("、", user.getHobbies()));
        }
        
        return summary.toString();
    }
}

💻 示例代码:完整的高级映射示例

示例 1:电商订单复杂映射

java 复制代码
// 订单实体类(复杂结构)
public class Order {
    private Long id;
    private String orderNo;
    private List<OrderItem> items;        // 订单项列表
    private Address shippingAddress;      // 收货地址
    private PaymentInfo paymentInfo;      // 支付信息
    private Long totalAmountCent;         // 总金额(分)
    private Integer status;               // 订单状态
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private String remark;
    
    // getter/setter...
}

// 订单项
public class OrderItem {
    private String productName;
    private Integer quantity;
    private Long unitPriceCent;           // 单价(分)
    
    // getter/setter...
}

// 支付信息
public class PaymentInfo {
    private String paymentMethod;         // 支付方式
    private String transactionId;         // 交易号
    private LocalDateTime paidAt;         // 支付时间
    
    // getter/setter...
}

// 订单展示 DTO
public class OrderDisplayDto {
    private Long id;
    private String orderNo;
    private String itemsSummary;          // 商品摘要
    private Integer totalItems;           // 商品总数
    private String totalAmount;           // 格式化总金额
    private String shippingAddress;       // 收货地址字符串
    private String paymentSummary;        // 支付摘要
    private String statusDesc;            // 状态描述
    private String timeInfo;              // 时间信息
    private String displayRemark;         // 显示备注
    
    // getter/setter...
}

/**
 * 订单复杂映射器
 */
@Mapper(
    componentModel = "spring",
    uses = {CustomTypeConverter.class}
)
public interface OrderDisplayMapper {
    
    @Mapping(target = "itemsSummary", 
             expression = "java(generateItemsSummary(order.getItems()))")
    
    @Mapping(target = "totalItems", 
             expression = "java(calculateTotalItems(order.getItems()))")
    
    @Mapping(target = "totalAmount", source = "totalAmountCent", 
             qualifiedByName = "centToYuan")
    
    @Mapping(target = "shippingAddress", source = "shippingAddress", 
             qualifiedByName = "addressToString")
    
    @Mapping(target = "paymentSummary", 
             expression = "java(generatePaymentSummary(order.getPaymentInfo()))")
    
    @Mapping(target = "statusDesc", source = "status", 
             qualifiedByName = "statusCodeToDescription")
    
    @Mapping(target = "timeInfo", 
             expression = "java(generateTimeInfo(order.getCreatedAt(), order.getUpdatedAt()))")
    
    @Mapping(target = "displayRemark", 
             expression = "java(formatRemark(order.getRemark()))")
    
    OrderDisplayDto toDisplayDto(Order order);
    
    /**
     * 生成商品摘要
     */
    default String generateItemsSummary(List<OrderItem> items) {
        if (items == null || items.isEmpty()) {
            return "无商品";
        }
        
        if (items.size() == 1) {
            return items.get(0).getProductName();
        } else if (items.size() <= 3) {
            return items.stream()
                    .map(OrderItem::getProductName)
                    .collect(Collectors.joining("、"));
        } else {
            return items.get(0).getProductName() + " 等" + items.size() + "件商品";
        }
    }
    
    /**
     * 计算商品总数
     */
    default Integer calculateTotalItems(List<OrderItem> items) {
        if (items == null) {
            return 0;
        }
        return items.stream()
                .mapToInt(OrderItem::getQuantity)
                .sum();
    }
    
    /**
     * 生成支付摘要
     */
    default String generatePaymentSummary(PaymentInfo paymentInfo) {
        if (paymentInfo == null) {
            return "未支付";
        }
        
        StringBuilder summary = new StringBuilder();
        summary.append(paymentInfo.getPaymentMethod());
        
        if (paymentInfo.getPaidAt() != null) {
            summary.append(",已于 ")
                   .append(paymentInfo.getPaidAt().format(
                       DateTimeFormatter.ofPattern("MM-dd HH:mm")
                   ))
                   .append(" 支付");
        }
        
        return summary.toString();
    }
    
    /**
     * 生成时间信息
     */
    default String generateTimeInfo(LocalDateTime createdAt, LocalDateTime updatedAt) {
        if (createdAt == null) {
            return "时间未知";
        }
        
        StringBuilder timeInfo = new StringBuilder();
        timeInfo.append("下单:")
                .append(createdAt.format(DateTimeFormatter.ofPattern("MM-dd HH:mm")));
        
        if (updatedAt != null && !updatedAt.equals(createdAt)) {
            timeInfo.append(",更新:")
                    .append(updatedAt.format(DateTimeFormatter.ofPattern("MM-dd HH:mm")));
        }
        
        return timeInfo.toString();
    }
    
    /**
     * 格式化备注
     */
    default String formatRemark(String remark) {
        if (remark == null || remark.trim().isEmpty()) {
            return "无备注";
        }
        
        // 限制备注长度
        if (remark.length() > 50) {
            return remark.substring(0, 47) + "...";
        }
        
        return remark;
    }
}

示例 2:条件映射和动态映射

java 复制代码
// 用户实体(包含敏感信息)
public class UserEntity {
    private Long id;
    private String username;
    private String email;
    private String phone;
    private String realName;
    private String idCard;                // 身份证号
    private BigDecimal balance;           // 账户余额
    private Integer userLevel;            // 用户等级 1-5
    private Boolean isEmailVerified;
    private Boolean isPhoneVerified;
    private LocalDateTime lastLoginAt;
    
    // getter/setter...
}

// 不同场景的 DTO
public class UserPublicDto {
    private Long id;
    private String username;
    private String email;                 // 可能需要脱敏
    private String phone;                 // 可能需要脱敏
    private String displayName;           // 显示名称
    private String levelDesc;             // 等级描述
    private String verificationStatus;    // 验证状态
    private String lastActiveTime;        // 最后活跃时间
    
    // getter/setter...
}

public class UserPrivateDto {
    private Long id;
    private String username;
    private String email;                 // 完整邮箱
    private String phone;                 // 完整手机号
    private String realName;              // 真实姓名
    private String idCardMasked;          // 脱敏身份证
    private String balance;               // 格式化余额
    private String levelDesc;
    private String verificationStatus;
    private String lastActiveTime;
    
    // getter/setter...
}

/**
 * 条件映射器 - 根据不同场景返回不同信息
 */
@Mapper(
    componentModel = "spring",
    uses = {CustomTypeConverter.class}
)
public interface UserConditionalMapper {
    
    /**
     * 公开信息映射 - 脱敏处理
     */
    @Mapping(target = "email", 
             expression = "java(maskEmail(user.getEmail()))")
    
    @Mapping(target = "phone", 
             expression = "java(maskPhone(user.getPhone()))")
    
    @Mapping(target = "displayName", 
             expression = "java(generateDisplayName(user.getUsername(), user.getRealName()))")
    
    @Mapping(target = "levelDesc", 
             expression = "java(mapUserLevel(user.getUserLevel()))")
    
    @Mapping(target = "verificationStatus", 
             expression = "java(generateVerificationStatus(user.getIsEmailVerified(), user.getIsPhoneVerified()))")
    
    @Mapping(target = "lastActiveTime", source = "lastLoginAt", 
             qualifiedByName = "timestampToRelativeTime")
    
    UserPublicDto toPublicDto(UserEntity user);
    
    /**
     * 私有信息映射 - 完整信息
     */
    @Mapping(target = "idCardMasked", 
             expression = "java(maskIdCard(user.getIdCard()))")
    
    @Mapping(target = "balance", source = "balance", 
             expression = "java(formatBalance(user.getBalance()))")
    
    @Mapping(target = "levelDesc", 
             expression = "java(mapUserLevel(user.getUserLevel()))")
    
    @Mapping(target = "verificationStatus", 
             expression = "java(generateVerificationStatus(user.getIsEmailVerified(), user.getIsPhoneVerified()))")
    
    @Mapping(target = "lastActiveTime", source = "lastLoginAt", 
             qualifiedByName = "timestampToRelativeTime")
    
    UserPrivateDto toPrivateDto(UserEntity user);
    
    /**
     * 邮箱脱敏
     */
    default String maskEmail(String email) {
        if (email == null || !email.contains("@")) {
            return email;
        }
        
        String[] parts = email.split("@");
        String username = parts[0];
        String domain = parts[1];
        
        if (username.length() <= 2) {
            return "*@" + domain;
        } else {
            return username.charAt(0) + "***" + username.charAt(username.length() - 1) + "@" + domain;
        }
    }
    
    /**
     * 手机号脱敏
     */
    default String maskPhone(String phone) {
        if (phone == null || phone.length() != 11) {
            return phone;
        }
        return phone.substring(0, 3) + "****" + phone.substring(7);
    }
    
    /**
     * 身份证脱敏
     */
    default String maskIdCard(String idCard) {
        if (idCard == null || idCard.length() < 8) {
            return idCard;
        }
        return idCard.substring(0, 4) + "**********" + idCard.substring(idCard.length() - 4);
    }
    
    /**
     * 生成显示名称
     */
    default String generateDisplayName(String username, String realName) {
        if (realName != null && !realName.trim().isEmpty()) {
            // 真实姓名脱敏
            if (realName.length() == 2) {
                return realName.charAt(0) + "*";
            } else if (realName.length() > 2) {
                return realName.charAt(0) + "*" + realName.charAt(realName.length() - 1);
            }
        }
        return username;
    }
    
    /**
     * 用户等级映射
     */
    default String mapUserLevel(Integer level) {
        if (level == null) return "普通用户";
        
        switch (level) {
            case 1: return "新手用户";
            case 2: return "普通用户";
            case 3: return "活跃用户";
            case 4: return "VIP用户";
            case 5: return "超级VIP";
            default: return "普通用户";
        }
    }
    
    /**
     * 生成验证状态
     */
    default String generateVerificationStatus(Boolean emailVerified, Boolean phoneVerified) {
        List<String> verified = new ArrayList<>();
        
        if (emailVerified != null && emailVerified) {
            verified.add("邮箱已验证");
        }
        if (phoneVerified != null && phoneVerified) {
            verified.add("手机已验证");
        }
        
        if (verified.isEmpty()) {
            return "未验证";
        } else {
            return String.join(",", verified);
        }
    }
    
    /**
     * 格式化余额
     */
    default String formatBalance(BigDecimal balance) {
        if (balance == null) {
            return "¥0.00";
        }
        
        DecimalFormat df = new DecimalFormat("#,##0.00");
        return "¥" + df.format(balance);
    }
}

🎬 效果演示:测试高级映射功能

创建测试控制器

java 复制代码
@RestController
@RequestMapping("/api/advanced-mapping")
public class AdvancedMappingController {
    
    @Autowired
    private TaskMapper taskMapper;
    
    @Autowired
    private UserDetailMapper userDetailMapper;
    
    @Autowired
    private OrderDisplayMapper orderDisplayMapper;
    
    @Autowired
    private UserConditionalMapper userConditionalMapper;
    
    /**
     * 演示自定义类型转换器
     */
    @GetMapping("/task")
    public TaskDto getTask() {
        // 创建地址对象
        Address address = new Address();
        address.setProvince("广东省");
        address.setCity("深圳市");
        address.setDistrict("南山区");
        address.setDetail("科技园南区");
        
        // 创建任务对象
        Task task = new Task();
        task.setId(1001L);
        task.setTitle("开发用户管理模块");
        task.setDescription("实现用户的增删改查功能");
        task.setStatus(1);                                    // 处理中
        task.setAmountInCent(500000L);                        // 5000元
        task.setAddress(address);
        task.setCreatedTimestamp(System.currentTimeMillis() - 86400000); // 1天前
        task.setUpdatedTimestamp(System.currentTimeMillis() - 3600000);  // 1小时前
        
        // 🎯 映射转换
        TaskDto dto = taskMapper.toDto(task);
        
        System.out.println("原始任务: " + task);
        System.out.println("映射结果: " + dto);
        
        return dto;
    }
    
    /**
     * 演示表达式映射
     */
    @GetMapping("/user-detail")
    public UserDetailDto getUserDetail() {
        User user = new User();
        user.setFirstName("张");
        user.setLastName("三");
        user.setEmail("zhangsan@example.com");
        user.setBirthDate(LocalDate.of(1990, 5, 15));
        user.setPhone("13800138000");
        user.setGender(1);                                    // 男
        user.setHobbies(Arrays.asList("编程", "阅读", "旅行"));
        user.setSalary(new BigDecimal("15000"));
        user.setIsVip(true);
        
        // 🎯 映射转换
        UserDetailDto dto = userDetailMapper.toDetailDto(user);
        
        System.out.println("原始用户: " + user);
        System.out.println("详情映射结果: " + dto);
        
        return dto;
    }
    
    /**
     * 演示复杂对象映射
     */
    @GetMapping("/order-display")
    public OrderDisplayDto getOrderDisplay() {
        // 创建订单项
        List<OrderItem> items = Arrays.asList(
            createOrderItem("iPhone 15 Pro", 1, 899900L),
            createOrderItem("AirPods Pro", 2, 179900L),
            createOrderItem("保护壳", 1, 9900L)
        );
        
        // 创建地址
        Address address = new Address();
        address.setProvince("北京市");
        address.setCity("北京市");
        address.setDistrict("朝阳区");
        address.setDetail("三里屯SOHO 1号楼");
        
        // 创建支付信息
        PaymentInfo paymentInfo = new PaymentInfo();
        paymentInfo.setPaymentMethod("微信支付");
        paymentInfo.setTransactionId("wx20250109123456");
        paymentInfo.setPaidAt(LocalDateTime.now().minusHours(2));
        
        // 创建订单
        Order order = new Order();
        order.setId(2001L);
        order.setOrderNo("ORD20250109001");
        order.setItems(items);
        order.setShippingAddress(address);
        order.setPaymentInfo(paymentInfo);
        order.setTotalAmountCent(1269600L);                   // 12696元
        order.setStatus(2);                                   // 已完成
        order.setCreatedAt(LocalDateTime.now().minusDays(1));
        order.setUpdatedAt(LocalDateTime.now().minusHours(1));
        order.setRemark("请尽快发货,谢谢!");
        
        // 🎯 映射转换
        OrderDisplayDto dto = orderDisplayMapper.toDisplayDto(order);
        
        System.out.println("原始订单: " + order);
        System.out.println("展示映射结果: " + dto);
        
        return dto;
    }
    
    /**
     * 演示条件映射 - 公开信息
     */
    @GetMapping("/user-public")
    public UserPublicDto getUserPublic() {
        UserEntity user = createSampleUser();
        
        // 🎯 公开信息映射(脱敏)
        UserPublicDto dto = userConditionalMapper.toPublicDto(user);
        
        System.out.println("原始用户: " + user);
        System.out.println("公开信息映射结果: " + dto);
        
        return dto;
    }
    
    /**
     * 演示条件映射 - 私有信息
     */
    @GetMapping("/user-private")
    public UserPrivateDto getUserPrivate() {
        UserEntity user = createSampleUser();
        
        // 🎯 私有信息映射(完整信息)
        UserPrivateDto dto = userConditionalMapper.toPrivateDto(user);
        
        System.out.println("原始用户: " + user);
        System.out.println("私有信息映射结果: " + dto);
        
        return dto;
    }
    
    // 辅助方法
    private OrderItem createOrderItem(String name, Integer quantity, Long unitPrice) {
        OrderItem item = new OrderItem();
        item.setProductName(name);
        item.setQuantity(quantity);
        item.setUnitPriceCent(unitPrice);
        return item;
    }
    
    private UserEntity createSampleUser() {
        UserEntity user = new UserEntity();
        user.setId(3001L);
        user.setUsername("zhangsan123");
        user.setEmail("zhangsan@example.com");
        user.setPhone("13800138000");
        user.setRealName("张三");
        user.setIdCard("110101199001011234");
        user.setBalance(new BigDecimal("12345.67"));
        user.setUserLevel(4);
        user.setIsEmailVerified(true);
        user.setIsPhoneVerified(true);
        user.setLastLoginAt(LocalDateTime.now().minusHours(3));
        return user;
    }
}

测试映射效果

bash 复制代码
# 测试自定义类型转换器
curl http://localhost:8080/api/advanced-mapping/task

# 期望返回:
# {
#   "id": 1001,
#   "title": "开发用户管理模块",
#   "description": "实现用户的增删改查功能",
#   "statusDesc": "处理中",
#   "amount": "¥5000.00",
#   "fullAddress": "广东省深圳市南山区科技园南区",
#   "createdTime": "1天前",
#   "updatedTime": "1小时前"
# }

# 测试表达式映射
curl http://localhost:8080/api/advanced-mapping/user-detail

# 期望返回:
# {
#   "fullName": "张 三",
#   "email": "zhangsan@example.com",
#   "age": 34,
#   "genderDesc": "男",
#   "phoneFormatted": "138****8000",
#   "hobbiesText": "编程, 阅读, 旅行",
#   "salaryLevel": "中级",
#   "membershipType": "VIP会员",
#   "profileSummary": "张 三,34岁,男,VIP会员,爱好:编程、阅读、旅行"
# }

# 测试公开信息映射(脱敏)
curl http://localhost:8080/api/advanced-mapping/user-public

# 期望返回:
# {
#   "id": 3001,
#   "username": "zhangsan123",
#   "email": "z***n@example.com",
#   "phone": "138****8000",
#   "displayName": "张*",
#   "levelDesc": "VIP用户",
#   "verificationStatus": "邮箱已验证,手机已验证",
#   "lastActiveTime": "3小时前"
# }

# 测试私有信息映射(完整信息)
curl http://localhost:8080/api/advanced-mapping/user-private

# 期望返回:
# {
#   "id": 3001,
#   "username": "zhangsan123",
#   "email": "zhangsan@example.com",
#   "phone": "13800138000",
#   "realName": "张三",
#   "idCardMasked": "1101**********1234",
#   "balance": "¥12,345.67",
#   "levelDesc": "VIP用户",
#   "verificationStatus": "邮箱已验证,手机已验证",
#   "lastActiveTime": "3小时前"
# }

❓ 常见问题

Q1: 自定义转换器和表达式映射有什么区别?

A: 两者适用场景不同:

java 复制代码
// 自定义转换器 - 适合复杂、可复用的转换逻辑
@Named("statusConverter")
public String convertStatus(Integer status) {
    // 复杂的转换逻辑
    return complexConversion(status);
}

// 表达式映射 - 适合简单、一次性的转换
@Mapping(target = "fullName", 
         expression = "java(user.getFirstName() + ' ' + user.getLastName())")

Q2: 如何处理转换过程中的异常?

A: 在转换方法中添加异常处理:

java 复制代码
@Named("safeConversion")
public String safeConvert(String input) {
    try {
        return complexConversion(input);
    } catch (Exception e) {
        log.warn("转换失败: {}", e.getMessage());
        return "转换失败";
    }
}

Q3: 表达式映射中可以调用 Spring Bean 吗?

A: 不能直接调用,但可以通过辅助方法:

java 复制代码
@Mapper(componentModel = "spring")
public abstract class UserMapper {
    
    @Autowired
    protected UserService userService;  // 注入 Spring Bean
    
    @Mapping(target = "extraInfo", 
             expression = "java(getExtraInfo(user.getId()))")
    public abstract UserDto toDto(User user);
    
    // 辅助方法中可以使用注入的 Bean
    protected String getExtraInfo(Long userId) {
        return userService.getExtraInfo(userId);
    }
}

Q4: 如何优化复杂映射的性能?

A: 几种优化策略:

  1. 缓存转换结果
java 复制代码
private final Map<Integer, String> statusCache = new ConcurrentHashMap<>();

@Named("cachedStatusConvert")
public String convertWithCache(Integer status) {
    return statusCache.computeIfAbsent(status, this::doConvert);
}
  1. 避免重复计算
java 复制代码
@Mapping(target = "age", expression = "java(calculateAge(user.getBirthDate()))")
@Mapping(target = "ageGroup", expression = "java(getAgeGroup(calculateAge(user.getBirthDate())))")

// 优化为:
@Mapping(target = "age", expression = "java(calculateAge(user.getBirthDate()))")
@Mapping(target = "ageGroup", expression = "java(getAgeGroup(target.getAge()))")

🎯 本章小结

通过本章学习,你应该掌握了:

  1. 类型转换器:内置转换器和自定义转换器的使用
  2. 表达式映射:使用 Java 表达式实现复杂映射逻辑
  3. 条件映射:根据不同场景返回不同的映射结果
  4. 性能优化:复杂映射的性能优化技巧

📖 下一步学习

在下一篇教程中,我们将学习:

  • 集合映射和列表转换
  • 嵌套对象的深度映射
  • Map 和复杂数据结构的处理
相关推荐
杨杨杨大侠2 小时前
Atlas Mapper 教程系列 (3/10):核心注解详解与基础映射
java·开源·github
tqs_123452 小时前
redis zset 处理大规模数据分页
java·算法·哈希算法
尚学教辅学习资料2 小时前
基于Spring Boot的家政服务管理系统+论文示例参考
java·spring boot·后端·java毕设
杨杨杨大侠2 小时前
Atlas Log 0.2.0 版本
java·github·apache log4j
快乐肚皮2 小时前
TransmittableThreadLocal:穿透线程边界的上下文传递艺术
java
渣哥2 小时前
别再无脑 synchronized 了!Java 锁优化的 7 个狠招
java
緈諨の約錠3 小时前
JVM基础篇以及JVM内存泄漏诊断与分析
java·jvm
Slaughter信仰3 小时前
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第十三章知识点问答(15题)
java·开发语言·jvm
绝无仅有3 小时前
大厂Redis高级面试题与答案
后端·面试·github