🎯 学习目标
通过本篇教程,你将学会:
- 掌握复杂类型转换的实现方法
- 学会创建和使用自定义类型转换器
- 理解表达式映射的高级用法
- 掌握条件映射和动态映射技巧
📋 概念讲解:高级映射机制
类型转换层次结构
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: 几种优化策略:
- 缓存转换结果:
java
private final Map<Integer, String> statusCache = new ConcurrentHashMap<>();
@Named("cachedStatusConvert")
public String convertWithCache(Integer status) {
return statusCache.computeIfAbsent(status, this::doConvert);
}
- 避免重复计算:
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()))")
🎯 本章小结
通过本章学习,你应该掌握了:
- 类型转换器:内置转换器和自定义转换器的使用
- 表达式映射:使用 Java 表达式实现复杂映射逻辑
- 条件映射:根据不同场景返回不同的映射结果
- 性能优化:复杂映射的性能优化技巧
📖 下一步学习
在下一篇教程中,我们将学习:
- 集合映射和列表转换
- 嵌套对象的深度映射
- Map 和复杂数据结构的处理