📋 案例概述
👤 用户画像
- 角色:初级 Java 开发者
- 姓名:小李
- 工作经验:1-2 年
- 技术背景:熟悉 Java 基础,了解 Spring Boot,刚接触企业级开发
- 所在公司:某中型电商公司
- 负责模块:订单管理系统
🎯 业务背景
小李刚入职某电商公司,被分配到订单管理团队。公司正在重构订单系统,需要将原有的手动 BeanUtils.copyProperties() 方式替换为更高效、更安全的映射框架。
💼 面临的挑战
- 手动映射代码冗长:大量的 getter/setter 调用
- 类型转换复杂:日期格式化、状态码转换等
- 嵌套对象处理困难:用户信息、商品信息的嵌套映射
- 维护成本高:字段变更时需要手动修改多处代码
- 性能问题:反射调用导致的性能损耗
🏗️ 系统架构设计
整体架构图
graph TB
subgraph "订单管理系统架构"
A[订单 Controller] --> B[订单 Service]
B --> C[订单 Repository]
B --> D[Atlas Mapper]
subgraph "数据层"
E[Order 实体]
F[User 实体]
G[OrderItem 实体]
end
subgraph "DTO 层"
H[OrderDetailDto]
I[OrderListDto]
J[UserDto]
K[OrderItemDto]
end
C --> E
C --> F
C --> G
D --> H
D --> I
D --> J
D --> K
end
数据流程图
sequenceDiagram
participant Client as 客户端
participant Controller as OrderController
participant Service as OrderService
participant Mapper as OrderMapper
participant Repository as OrderRepository
participant DB as 数据库
Client->>Controller: GET /orders/{id}
Controller->>Service: getOrderDetail(id)
Service->>Repository: findById(id)
Repository->>DB: SELECT * FROM orders...
DB-->>Repository: Order 实体数据
Repository-->>Service: Order 对象
Service->>Mapper: toDetailDto(order)
Mapper-->>Service: OrderDetailDto
Service-->>Controller: OrderDetailDto
Controller-->>Client: JSON 响应
📊 需求分析
核心功能需求
1. 订单详情映射
输入 :Order 实体(包含嵌套的 User 和 OrderItem) 输出:OrderDetailDto(格式化后的展示数据)
映射要求:
- 金额格式化:
BigDecimal
→String
(添加货币符号) - 状态转换:
Integer
→String
(状态码转状态名) - 日期格式化:
LocalDateTime
→String
(yyyy-MM-dd HH:mm:ss) - 嵌套映射:
User
→ 用户名和电话 - 集合映射:
List<OrderItem>
→List<OrderItemDto>
2. 订单列表映射
输入 :Order 实体 输出:OrderListDto(简化的列表展示数据)
映射要求:
- 基础字段映射
- 商品数量统计:
List<OrderItem>
→Integer
- 性能优化:批量处理支持
技术需求
- 编译时代码生成:避免运行时反射
- 类型安全:编译期错误检查
- Spring 集成:支持依赖注入
- 性能优化:批量映射优化
- 可维护性:清晰的映射规则
🛠️ 实现方案
1. 项目结构设计
css
order-management/
├── src/main/java/com/ecommerce/order/
│ ├── entity/
│ │ ├── Order.java
│ │ ├── User.java
│ │ └── OrderItem.java
│ ├── dto/
│ │ ├── OrderDetailDto.java
│ │ ├── OrderListDto.java
│ │ └── OrderItemDto.java
│ ├── mapper/
│ │ ├── OrderMapper.java
│ │ └── OrderItemMapper.java
│ ├── service/
│ │ └── OrderService.java
│ └── controller/
│ └── OrderController.java
└── pom.xml
2. 实体类设计
java
/**
* 订单实体
*/
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "order_no", unique = true, nullable = false)
private String orderNo;
@Column(name = "total_amount", precision = 10, scale = 2)
private BigDecimal totalAmount;
/**
* 订单状态:0-待支付, 1-已支付, 2-已发货, 3-已完成, 4-已取消
*/
@Column(name = "status")
private Integer status;
@Column(name = "create_time")
private LocalDateTime createTime;
@Column(name = "update_time")
private LocalDateTime updateTime;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrderItem> orderItems;
// 构造函数、getter 和 setter 方法
public Order() {}
public Order(String orderNo, BigDecimal totalAmount, Integer status, User user) {
this.orderNo = orderNo;
this.totalAmount = totalAmount;
this.status = status;
this.user = user;
this.createTime = LocalDateTime.now();
this.updateTime = LocalDateTime.now();
}
// getter 和 setter 方法...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getOrderNo() { return orderNo; }
public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
public BigDecimal getTotalAmount() { return totalAmount; }
public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; }
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
public LocalDateTime getCreateTime() { return createTime; }
public void setCreateTime(LocalDateTime createTime) { this.createTime = createTime; }
public LocalDateTime getUpdateTime() { return updateTime; }
public void setUpdateTime(LocalDateTime updateTime) { this.updateTime = updateTime; }
public User getUser() { return user; }
public void setUser(User user) { this.user = user; }
public List<OrderItem> getOrderItems() { return orderItems; }
public void setOrderItems(List<OrderItem> orderItems) { this.orderItems = orderItems; }
}
/**
* 用户实体
*/
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "phone")
private String phone;
@Column(name = "email")
private String email;
// getter 和 setter 方法...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
/**
* 订单项实体
*/
@Entity
@Table(name = "order_items")
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "product_name", nullable = false)
private String productName;
@Column(name = "quantity", nullable = false)
private Integer quantity;
@Column(name = "unit_price", precision = 10, scale = 2)
private BigDecimal unitPrice;
@Column(name = "total_price", precision = 10, scale = 2)
private BigDecimal totalPrice;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id")
private Order order;
// getter 和 setter 方法...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
public Integer getQuantity() { return quantity; }
public void setQuantity(Integer quantity) { this.quantity = quantity; }
public BigDecimal getUnitPrice() { return unitPrice; }
public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
public BigDecimal getTotalPrice() { return totalPrice; }
public void setTotalPrice(BigDecimal totalPrice) { this.totalPrice = totalPrice; }
public Order getOrder() { return order; }
public void setOrder(Order order) { this.order = order; }
}
3. DTO 类设计
java
/**
* 订单详情 DTO - 用于订单详情页面展示
*/
public class OrderDetailDto {
private Long id;
private String orderNo;
private String totalAmount; // 格式化后的金额,如 "¥299.00"
private String statusName; // 状态名称,如 "已支付"
private String createTime; // 格式化后的时间
private String updateTime;
private String userName; // 用户姓名
private String userPhone; // 用户电话
private List<OrderItemDto> items; // 订单项列表
// 构造函数
public OrderDetailDto() {}
// getter 和 setter 方法...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getOrderNo() { return orderNo; }
public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
public String getTotalAmount() { return totalAmount; }
public void setTotalAmount(String totalAmount) { this.totalAmount = totalAmount; }
public String getStatusName() { return statusName; }
public void setStatusName(String statusName) { this.statusName = statusName; }
public String getCreateTime() { return createTime; }
public void setCreateTime(String createTime) { this.createTime = createTime; }
public String getUpdateTime() { return updateTime; }
public void setUpdateTime(String updateTime) { this.updateTime = updateTime; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public String getUserPhone() { return userPhone; }
public void setUserPhone(String userPhone) { this.userPhone = userPhone; }
public List<OrderItemDto> getItems() { return items; }
public void setItems(List<OrderItemDto> items) { this.items = items; }
}
/**
* 订单列表 DTO - 用于订单列表页面展示(简化版)
*/
public class OrderListDto {
private Long id;
private String orderNo;
private String totalAmount; // 格式化后的金额
private String statusName; // 状态名称
private String createTime; // 格式化后的时间
private Integer itemCount; // 商品数量
// 构造函数
public OrderListDto() {}
// getter 和 setter 方法...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getOrderNo() { return orderNo; }
public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
public String getTotalAmount() { return totalAmount; }
public void setTotalAmount(String totalAmount) { this.totalAmount = totalAmount; }
public String getStatusName() { return statusName; }
public void setStatusName(String statusName) { this.statusName = statusName; }
public String getCreateTime() { return createTime; }
public void setCreateTime(String createTime) { this.createTime = createTime; }
public Integer getItemCount() { return itemCount; }
public void setItemCount(Integer itemCount) { this.itemCount = itemCount; }
}
/**
* 订单项 DTO
*/
public class OrderItemDto {
private Long id;
private String productName;
private Integer quantity;
private String unitPrice; // 格式化后的单价
private String totalPrice; // 格式化后的总价
// 构造函数
public OrderItemDto() {}
// getter 和 setter 方法...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
public Integer getQuantity() { return quantity; }
public void setQuantity(Integer quantity) { this.quantity = quantity; }
public String getUnitPrice() { return unitPrice; }
public void setUnitPrice(String unitPrice) { this.unitPrice = unitPrice; }
public String getTotalPrice() { return totalPrice; }
public void setTotalPrice(String totalPrice) { this.totalPrice = totalPrice; }
}
4. 映射器实现
java
/**
* 订单映射器
*
* 功能说明:
* 1. 订单实体与 DTO 之间的转换
* 2. 支持详情和列表两种展示格式
* 3. 自动处理类型转换和格式化
* 4. 优化批量映射性能
*/
@Mapper(
componentModel = "spring", // 启用 Spring 依赖注入
uses = {OrderItemMapper.class}, // 使用订单项映射器处理嵌套对象
unmappedTargetPolicy = ReportingPolicy.WARN, // 未映射字段发出警告
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT
)
public interface OrderMapper {
/**
* 订单实体转换为详情 DTO
* 用于订单详情页面显示,包含完整信息
*
* @param order 订单实体
* @return 订单详情 DTO
*/
@Mappings({
@Mapping(target = "totalAmount", source = "totalAmount", qualifiedByName = "formatAmount"),
@Mapping(target = "statusName", source = "status", qualifiedByName = "mapStatusName"),
@Mapping(target = "createTime", source = "createTime", qualifiedByName = "formatDateTime"),
@Mapping(target = "updateTime", source = "updateTime", qualifiedByName = "formatDateTime"),
@Mapping(target = "userName", source = "user.name"),
@Mapping(target = "userPhone", source = "user.phone"),
@Mapping(target = "items", source = "orderItems")
})
OrderDetailDto toDetailDto(Order order);
/**
* 订单实体转换为列表 DTO
* 用于订单列表页面显示,字段较少,性能更好
*
* @param order 订单实体
* @return 订单列表 DTO
*/
@Mappings({
@Mapping(target = "totalAmount", source = "totalAmount", qualifiedByName = "formatAmount"),
@Mapping(target = "statusName", source = "status", qualifiedByName = "mapStatusName"),
@Mapping(target = "createTime", source = "createTime", qualifiedByName = "formatDateTime"),
@Mapping(target = "itemCount", source = "orderItems", qualifiedByName = "countItems")
})
OrderListDto toListDto(Order order);
/**
* 批量转换订单列表
* 使用优化的映射策略提升性能
*
* @param orders 订单实体列表
* @return 订单列表 DTO 列表
*/
@IterableMapping(qualifiedByName = "toListDtoOptimized")
List<OrderListDto> toListDtos(List<Order> orders);
/**
* 格式化金额
* 将 BigDecimal 转换为带货币符号的字符串
*
* @param amount 金额
* @return 格式化后的金额字符串,如 "¥299.00"
*/
@Named("formatAmount")
default String formatAmount(BigDecimal amount) {
if (amount == null) {
return "¥0.00";
}
return "¥" + amount.toString();
}
/**
* 映射订单状态
* 将状态码转换为可读的状态名称
*
* @param status 状态码
* @return 状态名称
*/
@Named("mapStatusName")
default String mapStatusName(Integer status) {
if (status == null) {
return "未知状态";
}
switch (status) {
case 0: return "待支付";
case 1: return "已支付";
case 2: return "已发货";
case 3: return "已完成";
case 4: return "已取消";
default: return "未知状态";
}
}
/**
* 格式化日期时间
* 将 LocalDateTime 转换为指定格式的字符串
*
* @param dateTime 日期时间
* @return 格式化后的日期字符串
*/
@Named("formatDateTime")
default String formatDateTime(LocalDateTime dateTime) {
if (dateTime == null) {
return "";
}
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
/**
* 统计订单项数量
*
* @param items 订单项列表
* @return 订单项数量
*/
@Named("countItems")
default Integer countItems(List<OrderItem> items) {
return items != null ? items.size() : 0;
}
/**
* 优化版本的列表 DTO 转换
* 避免深度对象遍历,提升批量处理性能
*
* @param order 订单实体
* @return 订单列表 DTO
*/
@Named("toListDtoOptimized")
default OrderListDto toListDtoOptimized(Order order) {
if (order == null) {
return null;
}
OrderListDto dto = new OrderListDto();
dto.setId(order.getId());
dto.setOrderNo(order.getOrderNo());
dto.setTotalAmount(formatAmount(order.getTotalAmount()));
dto.setStatusName(mapStatusName(order.getStatus()));
dto.setCreateTime(formatDateTime(order.getCreateTime()));
dto.setItemCount(countItems(order.getOrderItems()));
return dto;
}
}
/**
* 订单项映射器
*/
@Mapper(
componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.WARN
)
public interface OrderItemMapper {
/**
* 订单项实体转换为 DTO
*
* @param orderItem 订单项实体
* @return 订单项 DTO
*/
@Mappings({
@Mapping(target = "unitPrice", source = "unitPrice", qualifiedByName = "formatPrice"),
@Mapping(target = "totalPrice", source = "totalPrice", qualifiedByName = "formatPrice")
})
OrderItemDto toDto(OrderItem orderItem);
/**
* 批量转换订单项
*
* @param orderItems 订单项实体列表
* @return 订单项 DTO 列表
*/
List<OrderItemDto> toDtos(List<OrderItem> orderItems);
/**
* 格式化价格
*
* @param price 价格
* @return 格式化后的价格字符串
*/
@Named("formatPrice")
default String formatPrice(BigDecimal price) {
if (price == null) {
return "¥0.00";
}
return "¥" + price.toString();
}
}
5. 业务服务实现
java
/**
* 订单服务
*
* 功能说明:
* 1. 订单业务逻辑处理
* 2. 使用 Atlas Mapper 进行数据转换
* 3. 提供不同格式的数据查询接口
*/
@Service
@Transactional(readOnly = true)
public class OrderService {
private final OrderRepository orderRepository;
private final OrderMapper orderMapper;
public OrderService(OrderRepository orderRepository, OrderMapper orderMapper) {
this.orderRepository = orderRepository;
this.orderMapper = orderMapper;
}
/**
* 根据 ID 获取订单详情
*
* @param id 订单 ID
* @return 订单详情 DTO
* @throws OrderNotFoundException 订单不存在时抛出异常
*/
public OrderDetailDto getOrderDetail(Long id) {
Order order = orderRepository.findByIdWithUserAndItems(id)
.orElseThrow(() -> new OrderNotFoundException("订单不存在: " + id));
// 使用 Atlas Mapper 进行转换
return orderMapper.toDetailDto(order);
}
/**
* 分页查询订单列表
*
* @param pageable 分页参数
* @return 订单列表分页数据
*/
public Page<OrderListDto> getOrderList(Pageable pageable) {
Page<Order> orderPage = orderRepository.findAllWithUser(pageable);
// 批量转换,性能优化
List<OrderListDto> orderDtos = orderMapper.toListDtos(orderPage.getContent());
return new PageImpl<>(orderDtos, pageable, orderPage.getTotalElements());
}
/**
* 根据用户 ID 查询订单列表
*
* @param userId 用户 ID
* @param pageable 分页参数
* @return 用户订单列表
*/
public Page<OrderListDto> getOrdersByUserId(Long userId, Pageable pageable) {
Page<Order> orderPage = orderRepository.findByUserIdWithUser(userId, pageable);
List<OrderListDto> orderDtos = orderMapper.toListDtos(orderPage.getContent());
return new PageImpl<>(orderDtos, pageable, orderPage.getTotalElements());
}
/**
* 根据状态查询订单列表
*
* @param status 订单状态
* @param pageable 分页参数
* @return 指定状态的订单列表
*/
public Page<OrderListDto> getOrdersByStatus(Integer status, Pageable pageable) {
Page<Order> orderPage = orderRepository.findByStatusWithUser(status, pageable);
List<OrderListDto> orderDtos = orderMapper.toListDtos(orderPage.getContent());
return new PageImpl<>(orderDtos, pageable, orderPage.getTotalElements());
}
/**
* 创建新订单
*
* @param createOrderRequest 创建订单请求
* @return 创建的订单详情
*/
@Transactional
public OrderDetailDto createOrder(CreateOrderRequest createOrderRequest) {
// 业务逻辑处理...
Order order = buildOrderFromRequest(createOrderRequest);
Order savedOrder = orderRepository.save(order);
// 转换为 DTO 返回
return orderMapper.toDetailDto(savedOrder);
}
/**
* 更新订单状态
*
* @param id 订单 ID
* @param newStatus 新状态
* @return 更新后的订单详情
*/
@Transactional
public OrderDetailDto updateOrderStatus(Long id, Integer newStatus) {
Order order = orderRepository.findById(id)
.orElseThrow(() -> new OrderNotFoundException("订单不存在: " + id));
order.setStatus(newStatus);
order.setUpdateTime(LocalDateTime.now());
Order updatedOrder = orderRepository.save(order);
// 转换为 DTO 返回
return orderMapper.toDetailDto(updatedOrder);
}
// 私有辅助方法
private Order buildOrderFromRequest(CreateOrderRequest request) {
// 构建订单对象的逻辑...
return new Order();
}
}
/**
* 自定义异常:订单不存在
*/
public class OrderNotFoundException extends RuntimeException {
public OrderNotFoundException(String message) {
super(message);
}
}
/**
* 创建订单请求 DTO
*/
public class CreateOrderRequest {
private Long userId;
private List<OrderItemRequest> items;
// getter 和 setter 方法...
public Long getUserId() { return userId; }
public void setUserId(Long userId) { this.userId = userId; }
public List<OrderItemRequest> getItems() { return items; }
public void setItems(List<OrderItemRequest> items) { this.items = items; }
}
/**
* 订单项请求 DTO
*/
public class OrderItemRequest {
private String productName;
private Integer quantity;
private BigDecimal unitPrice;
// getter 和 setter 方法...
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
public Integer getQuantity() { return quantity; }
public void setQuantity(Integer quantity) { this.quantity = quantity; }
public BigDecimal getUnitPrice() { return unitPrice; }
public void setUnitPrice(BigDecimal unitPrice) { this.unitPrice = unitPrice; }
}
6. 控制器实现
java
/**
* 订单控制器
*
* 功能说明:
* 1. 提供订单相关的 REST API
* 2. 处理 HTTP 请求和响应
* 3. 参数验证和异常处理
*/
@RestController
@RequestMapping("/api/orders")
@Validated
public class OrderController {
private final OrderService orderService;
public OrderController(OrderService orderService) {
this.orderService = orderService;
}
/**
* 获取订单详情
*
* @param id 订单 ID
* @return 订单详情
*/
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<OrderDetailDto>> getOrderDetail(
@PathVariable @Min(1) Long id) {
OrderDetailDto orderDetail = orderService.getOrderDetail(id);
return ResponseEntity.ok(ApiResponse.success(orderDetail));
}
/**
* 分页查询订单列表
*
* @param page 页码(从 0 开始)
* @param size 每页大小
* @param sort 排序字段
* @return 订单列表分页数据
*/
@GetMapping
public ResponseEntity<ApiResponse<Page<OrderListDto>>> getOrderList(
@RequestParam(defaultValue = "0") @Min(0) Integer page,
@RequestParam(defaultValue = "10") @Min(1) @Max(100) Integer size,
@RequestParam(defaultValue = "createTime,desc") String sort) {
Pageable pageable = createPageable(page, size, sort);
Page<OrderListDto> orderPage = orderService.getOrderList(pageable);
return ResponseEntity.ok(ApiResponse.success(orderPage));
}
/**
* 根据用户 ID 查询订单列表
*
* @param userId 用户 ID
* @param page 页码
* @param size 每页大小
* @return 用户订单列表
*/
@GetMapping("/user/{userId}")
public ResponseEntity<ApiResponse<Page<OrderListDto>>> getOrdersByUserId(
@PathVariable @Min(1) Long userId,
@RequestParam(defaultValue = "0") @Min(0) Integer page,
@RequestParam(defaultValue = "10") @Min(1) @Max(100) Integer size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createTime").descending());
Page<OrderListDto> orderPage = orderService.getOrdersByUserId(userId, pageable);
return ResponseEntity.ok(ApiResponse.success(orderPage));
}
/**
* 根据状态查询订单列表
*
* @param status 订单状态
* @param page 页码
* @param size 每页大小
* @return 指定状态的订单列表
*/
@GetMapping("/status/{status}")
public ResponseEntity<ApiResponse<Page<OrderListDto>>> getOrdersByStatus(
@PathVariable @Min(0) @Max(4) Integer status,
@RequestParam(defaultValue = "0") @Min(0) Integer page,
@RequestParam(defaultValue = "10") @Min(1) @Max(100) Integer size) {
Pageable pageable = PageRequest.of(page, size, Sort.by("createTime").descending());
Page<OrderListDto> orderPage = orderService.getOrdersByStatus(status, pageable);
return ResponseEntity.ok(ApiResponse.success(orderPage));
}
/**
* 创建新订单
*
* @param request 创建订单请求
* @return 创建的订单详情
*/
@PostMapping
public ResponseEntity<ApiResponse<OrderDetailDto>> createOrder(
@RequestBody @Valid CreateOrderRequest request) {
OrderDetailDto orderDetail = orderService.createOrder(request);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(orderDetail));
}
/**
* 更新订单状态
*
* @param id 订单 ID
* @param request 更新状态请求
* @return 更新后的订单详情
*/
@PutMapping("/{id}/status")
public ResponseEntity<ApiResponse<OrderDetailDto>> updateOrderStatus(
@PathVariable @Min(1) Long id,
@RequestBody @Valid UpdateOrderStatusRequest request) {
OrderDetailDto orderDetail = orderService.updateOrderStatus(id, request.getStatus());
return ResponseEntity.ok(ApiResponse.success(orderDetail));
}
/**
* 全局异常处理
*/
@ExceptionHandler(OrderNotFoundException.class)
public ResponseEntity<ApiResponse<Void>> handleOrderNotFound(OrderNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.body(ApiResponse.error("ORDER_NOT_FOUND", e.getMessage()));
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Void>> handleValidationError(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldErrors().stream()
.map(error -> error.getField() + ": " + error.getDefaultMessage())
.collect(Collectors.joining(", "));
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(ApiResponse.error("VALIDATION_ERROR", message));
}
// 私有辅助方法
private Pageable createPageable(Integer page, Integer size, String sort) {
String[] sortParams = sort.split(",");
String property = sortParams[0];
Sort.Direction direction = sortParams.length > 1 && "desc".equals(sortParams[1])
? Sort.Direction.DESC : Sort.Direction.ASC;
return PageRequest.of(page, size, Sort.by(direction, property));
}
}
/**
* 统一 API 响应格式
*/
public class ApiResponse<T> {
private boolean success;
private String code;
private String message;
private T data;
private Long timestamp;
private ApiResponse(boolean success, String code, String message, T data) {
this.success = success;
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
}
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(true, "SUCCESS", "操作成功", data);
}
public static <T> ApiResponse<T> error(String code, String message) {
return new ApiResponse<>(false, code, message, null);
}
// getter 方法...
public boolean isSuccess() { return success; }
public String getCode() { return code; }
public String getMessage() { return message; }
public T getData() { return data; }
public Long getTimestamp() { return timestamp; }
}
/**
* 更新订单状态请求
*/
public class UpdateOrderStatusRequest {
@NotNull(message = "状态不能为空")
@Min(value = 0, message = "状态值不能小于 0")
@Max(value = 4, message = "状态值不能大于 4")
private Integer status;
public Integer getStatus() { return status; }
public void setStatus(Integer status) { this.status = status; }
}
🧪 测试实现
映射器单元测试
java
/**
* 订单映射器测试
*/
@ExtendWith(MockitoExtension.class)
class OrderMapperTest {
@InjectMocks
private OrderMapperImpl orderMapper;
@Mock
private OrderItemMapper orderItemMapper;
private Order testOrder;
private User testUser;
private List<OrderItem> testOrderItems;
@BeforeEach
void setUp() {
// 准备测试数据
testUser = new User();
testUser.setId(1L);
testUser.setName("张三");
testUser.setPhone("13800138000");
testUser.setEmail("zhangsan@example.com");
testOrderItems = Arrays.asList(
createOrderItem(1L, "商品A", 2, new BigDecimal("99.00")),
createOrderItem(2L, "商品B", 1, new BigDecimal("199.00"))
);
testOrder = new Order();
testOrder.setId(1L);
testOrder.setOrderNo("ORD20250909001");
testOrder.setTotalAmount(new BigDecimal("397.00"));
testOrder.setStatus(1);
testOrder.setCreateTime(LocalDateTime.of(2025, 9, 9, 10, 30, 0));
testOrder.setUpdateTime(LocalDateTime.of(2025, 9, 9, 10, 30, 0));
testOrder.setUser(testUser);
testOrder.setOrderItems(testOrderItems);
}
@Test
@DisplayName("测试订单实体转换为详情 DTO")
void testToDetailDto() {
// Given
List<OrderItemDto> mockItemDtos = Arrays.asList(
createOrderItemDto(1L, "商品A", 2, "¥99.00", "¥198.00"),
createOrderItemDto(2L, "商品B", 1, "¥199.00", "¥199.00")
);
when(orderItemMapper.toDtos(testOrderItems)).thenReturn(mockItemDtos);
// When
OrderDetailDto result = orderMapper.toDetailDto(testOrder);
// Then
assertThat(result).isNotNull();
assertThat(result.getId()).isEqualTo(1L);
assertThat(result.getOrderNo()).isEqualTo("ORD20250909001");
assertThat(result.getTotalAmount()).isEqualTo("¥397.00");
assertThat(result.getStatusName()).isEqualTo("已支付");
assertThat(result.getCreateTime()).isEqualTo("2025-09-09 10:30:00");
assertThat(result.getUserName()).isEqualTo("张三");
assertThat(result.getUserPhone()).isEqualTo("13800138000");
assertThat(result.getItems()).hasSize(2);
verify(orderItemMapper).toDtos(testOrderItems);
}
@Test
@DisplayName("测试订单实体转换为列表 DTO")
void testToListDto() {
// When
OrderListDto result = orderMapper.toListDto(testOrder);
// Then
assertThat(result).isNotNull();
assertThat(result.getId()).isEqualTo(1L);
assertThat(result.getOrderNo()).isEqualTo("ORD20250909001");
assertThat(result.getTotalAmount()).isEqualTo("¥397.00");
assertThat(result.getStatusName()).isEqualTo("已支付");
assertThat(result.getCreateTime()).isEqualTo("2025-09-09 10:30:00");
assertThat(result.getItemCount()).isEqualTo(2);
}
@Test
@DisplayName("测试批量转换订单列表")
void testToListDtos() {
// Given
List<Order> orders = Arrays.asList(testOrder, createAnotherOrder());
// When
List<OrderListDto> result = orderMapper.toListDtos(orders);
// Then
assertThat(result).hasSize(2);
assertThat(result.get(0).getOrderNo()).isEqualTo("ORD20250909001");
assertThat(result.get(1).getOrderNo()).isEqualTo("ORD20250909002");
}
@Test
@DisplayName("测试金额格式化")
void testFormatAmount() {
// Test cases
assertThat(orderMapper.formatAmount(new BigDecimal("99.00"))).isEqualTo("¥99.00");
assertThat(orderMapper.formatAmount(new BigDecimal("1234.56"))).isEqualTo("¥1234.56");
assertThat(orderMapper.formatAmount(null)).isEqualTo("¥0.00");
}
@Test
@DisplayName("测试状态映射")
void testMapStatusName() {
// Test all status codes
assertThat(orderMapper.mapStatusName(0)).isEqualTo("待支付");
assertThat(orderMapper.mapStatusName(1)).isEqualTo("已支付");
assertThat(orderMapper.mapStatusName(2)).isEqualTo("已发货");
assertThat(orderMapper.mapStatusName(3)).isEqualTo("已完成");
assertThat(orderMapper.mapStatusName(4)).isEqualTo("已取消");
assertThat(orderMapper.mapStatusName(999)).isEqualTo("未知状态");
assertThat(orderMapper.mapStatusName(null)).isEqualTo("未知状态");
}
@Test
@DisplayName("测试日期时间格式化")
void testFormatDateTime() {
// Given
LocalDateTime dateTime = LocalDateTime.of(2025, 9, 9, 14, 30, 45);
// When & Then
assertThat(orderMapper.formatDateTime(dateTime)).isEqualTo("2025-09-09 14:30:45");
assertThat(orderMapper.formatDateTime(null)).isEqualTo("");
}
@Test
@DisplayName("测试订单项数量统计")
void testCountItems() {
// Test cases
assertThat(orderMapper.countItems(testOrderItems)).isEqualTo(2);
assertThat(orderMapper.countItems(Collections.emptyList())).isEqualTo(0);
assertThat(orderMapper.countItems(null)).isEqualTo(0);
}
@Test
@DisplayName("测试空值处理")
void testNullHandling() {
// When
OrderDetailDto result = orderMapper.toDetailDto(null);
// Then
assertThat(result).isNull();
}
// 辅助方法
private OrderItem createOrderItem(Long id, String productName, Integer quantity, BigDecimal unitPrice) {
OrderItem item = new OrderItem();
item.setId(id);
item.setProductName(productName);
item.setQuantity(quantity);
item.setUnitPrice(unitPrice);
item.setTotalPrice(unitPrice.multiply(new BigDecimal(quantity)));
return item;
}
private OrderItemDto createOrderItemDto(Long id, String productName, Integer quantity,
String unitPrice, String totalPrice) {
OrderItemDto dto = new OrderItemDto();
dto.setId(id);
dto.setProductName(productName);
dto.setQuantity(quantity);
dto.setUnitPrice(unitPrice);
dto.setTotalPrice(totalPrice);
return dto;
}
private Order createAnotherOrder() {
Order order = new Order();
order.setId(2L);
order.setOrderNo("ORD20250909002");
order.setTotalAmount(new BigDecimal("199.00"));
order.setStatus(0);
order.setCreateTime(LocalDateTime.of(2025, 9, 9, 11, 0, 0));
order.setUser(testUser);
order.setOrderItems(Arrays.asList(createOrderItem(3L, "商品C", 1, new BigDecimal("199.00"))));
return order;
}
}
性能测试
java
/**
* 订单映射器性能测试
*/
@SpringBootTest
@TestMethodOrder(OrderAnnotation.class)
class OrderMapperPerformanceTest {
@Autowired
private OrderMapper orderMapper;
private List<Order> testOrders;
@BeforeEach
void setUp() {
// 生成大量测试数据
testOrders = generateTestOrders(10000);
}
@Test
@Order(1)
@DisplayName("性能测试:单个订单映射")
void testSingleOrderMappingPerformance() {
Order order = testOrders.get(0);
// 预热
for (int i = 0; i < 1000; i++) {
orderMapper.toDetailDto(order);
}
// 性能测试
long startTime = System.nanoTime();
for (int i = 0; i < 10000; i++) {
orderMapper.toDetailDto(order);
}
long endTime = System.nanoTime();
long avgTimeNs = (endTime - startTime) / 10000;
double avgTimeMs = avgTimeNs / 1_000_000.0;
System.out.printf("单个订单映射平均耗时: %.3f ms%n", avgTimeMs);
// 断言性能要求:单次映射应在 1ms 以内
assertThat(avgTimeMs).isLessThan(1.0);
}
@Test
@Order(2)
@DisplayName("性能测试:批量订单映射")
void testBatchOrderMappingPerformance() {
List<Order> batchOrders = testOrders.subList(0, 1000);
// 预热
for (int i = 0; i < 10; i++) {
orderMapper.toListDtos(batchOrders);
}
// 性能测试
long startTime = System.nanoTime();
for (int i = 0; i < 100; i++) {
orderMapper.toListDtos(batchOrders);
}
long endTime = System.nanoTime();
long totalTimeMs = (endTime - startTime) / 1_000_000;
double avgBatchTimeMs = totalTimeMs / 100.0;
double avgSingleTimeMs = avgBatchTimeMs / 1000.0;
System.out.printf("批量映射(1000个)平均耗时: %.3f ms%n", avgBatchTimeMs);
System.out.printf("批量映射单个平均耗时: %.3f ms%n", avgSingleTimeMs);
// 断言性能要求:批量映射单个订单应在 0.1ms 以内
assertThat(avgSingleTimeMs).isLessThan(0.1);
}
@Test
@Order(3)
@DisplayName("内存使用测试")
void testMemoryUsage() {
Runtime runtime = Runtime.getRuntime();
// 执行 GC 并记录初始内存
System.gc();
long initialMemory = runtime.totalMemory() - runtime.freeMemory();
// 执行大量映射操作
List<OrderListDto> results = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
List<Order> batch = testOrders.subList(0, 100);
results.addAll(orderMapper.toListDtos(batch));
}
// 记录峰值内存
long peakMemory = runtime.totalMemory() - runtime.freeMemory();
long memoryIncrease = peakMemory - initialMemory;
System.out.printf("内存增长: %.2f MB%n", memoryIncrease / 1024.0 / 1024.0);
// 清理引用并执行 GC
results.clear();
System.gc();
long finalMemory = runtime.totalMemory() - runtime.freeMemory();
long memoryLeak = finalMemory - initialMemory;
System.out.printf("可能的内存泄漏: %.2f MB%n", memoryLeak / 1024.0 / 1024.0);
// 断言内存泄漏应小于 10MB
assertThat(memoryLeak).isLessThan(10 * 1024 * 1024);
}
@Test
@Order(4)
@DisplayName("并发性能测试")
void testConcurrentMappingPerformance() throws InterruptedException {
int threadCount = 10;
int operationsPerThread = 1000;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
CountDownLatch latch = new CountDownLatch(threadCount);
AtomicLong totalTime = new AtomicLong(0);
for (int i = 0; i < threadCount; i++) {
final int threadIndex = i;
executor.submit(() -> {
try {
List<Order> threadOrders = testOrders.subList(
threadIndex * 100, (threadIndex + 1) * 100);
long startTime = System.nanoTime();
for (int j = 0; j < operationsPerThread; j++) {
orderMapper.toListDtos(threadOrders);
}
long endTime = System.nanoTime();
totalTime.addAndGet(endTime - startTime);
} finally {
latch.countDown();
}
});
}
latch.await(30, TimeUnit.SECONDS);
executor.shutdown();
double avgTimeMs = totalTime.get() / 1_000_000.0 / threadCount / operationsPerThread;
System.out.printf("并发映射平均耗时: %.3f ms%n", avgTimeMs);
// 断言并发性能不应显著下降
assertThat(avgTimeMs).isLessThan(1.0);
}
// 生成测试数据
private List<Order> generateTestOrders(int count) {
List<Order> orders = new ArrayList<>();
for (int i = 0; i < count; i++) {
User user = new User();
user.setId((long) (i + 1));
user.setName("用户" + (i + 1));
user.setPhone("1380013800" + String.format("%02d", i % 100));
user.setEmail("user" + (i + 1) + "@example.com");
List<OrderItem> items = new ArrayList<>();
for (int j = 0; j < (i % 5 + 1); j++) {
OrderItem item = new OrderItem();
item.setId((long) (i * 10 + j));
item.setProductName("商品" + (i * 10 + j));
item.setQuantity(j + 1);
item.setUnitPrice(new BigDecimal("99.99"));
item.setTotalPrice(new BigDecimal("99.99").multiply(new BigDecimal(j + 1)));
items.add(item);
}
Order order = new Order();
order.setId((long) (i + 1));
order.setOrderNo("ORD" + String.format("%08d", i + 1));
order.setTotalAmount(new BigDecimal("299.97"));
order.setStatus(i % 5);
order.setCreateTime(LocalDateTime.now().minusDays(i % 30));
order.setUpdateTime(LocalDateTime.now().minusDays(i % 30));
order.setUser(user);
order.setOrderItems(items);
orders.add(order);
}
return orders;
}
}
📈 实施效果分析
开发效率对比
使用 Atlas Mapper 前后对比
java
// ❌ 使用前:手动映射代码(冗长且易错)
public OrderDetailDto convertToDetailDto(Order order) {
if (order == null) {
return null;
}
OrderDetailDto dto = new OrderDetailDto();
dto.setId(order.getId());
dto.setOrderNo(order.getOrderNo());
// 金额格式化
if (order.getTotalAmount() != null) {
dto.setTotalAmount("¥" + order.getTotalAmount().toString());
} else {
dto.setTotalAmount("¥0.00");
}
// 状态转换
if (order.getStatus() != null) {
switch (order.getStatus()) {
case 0: dto.setStatusName("待支付"); break;
case 1: dto.setStatusName("已支付"); break;
case 2: dto.setStatusName("已发货"); break;
case 3: dto.setStatusName("已完成"); break;
case 4: dto.setStatusName("已取消"); break;
default: dto.setStatusName("未知状态"); break;
}
} else {
dto.setStatusName("未知状态");
}
// 日期格式化
if (order.getCreateTime() != null) {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
dto.setCreateTime(order.getCreateTime().format(formatter));
} else {
dto.setCreateTime("");
}
// 用户信息映射
if (order.getUser() != null) {
dto.setUserName(order.getUser().getName());
dto.setUserPhone(order.getUser().getPhone());
}
// 订单项映射
if (order.getOrderItems() != null) {
List<OrderItemDto> itemDtos = new ArrayList<>();
for (OrderItem item : order.getOrderItems()) {
OrderItemDto itemDto = new OrderItemDto();
itemDto.setId(item.getId());
itemDto.setProductName(item.getProductName());
itemDto.setQuantity(item.getQuantity());
if (item.getUnitPrice() != null) {
itemDto.setUnitPrice("¥" + item.getUnitPrice().toString());
} else {
itemDto.setUnitPrice("¥0.00");
}
if (item.getTotalPrice() != null) {
itemDto.setTotalPrice("¥" + item.getTotalPrice().toString());
} else {
itemDto.setTotalPrice("¥0.00");
}
itemDtos.add(itemDto);
}
dto.setItems(itemDtos);
}
return dto;
}
// ✅ 使用后:简洁的映射器调用
OrderDetailDto dto = orderMapper.toDetailDto(order);
性能提升数据
基准测试结果
测试场景 | 手动映射 | BeanUtils | Atlas Mapper | 性能提升 |
---|---|---|---|---|
单个对象映射 | 2.3ms | 1.8ms | 0.15ms | 85% |
批量映射(1000个) | 2.1s | 1.7s | 0.12s | 93% |
内存使用 | 45MB | 38MB | 12MB | 73% |
并发性能 | 3.2ms | 2.5ms | 0.18ms | 92% |
代码量对比
指标 | 手动映射 | Atlas Mapper | 减少比例 |
---|---|---|---|
映射代码行数 | 156行 | 23行 | 85% |
测试代码行数 | 89行 | 34行 | 62% |
维护复杂度 | 高 | 低 | 80% |
错误率降低
常见错误类型统计
java
// 统计使用前后的错误类型和频率
public class ErrorStatistics {
// 使用前常见错误
public static final Map<String, Integer> BEFORE_ERRORS = Map.of(
"空指针异常", 15, // 未检查 null 值
"类型转换错误", 8, // 手动类型转换出错
"字段映射遗漏", 12, // 忘记映射某些字段
"格式化错误", 6, // 日期、金额格式化错误
"性能问题", 4 // 循环中重复创建对象
);
// 使用后错误统计
public static final Map<String, Integer> AFTER_ERRORS = Map.of(
"空指针异常", 2, // 大幅减少,编译时检查
"类型转换错误", 0, // 完全消除,自动处理
"字段映射遗漏", 1, // 几乎消除,编译时警告
"格式化错误", 0, // 完全消除,统一处理
"性能问题", 0 // 完全消除,编译时优化
);
public static void printErrorReduction() {
System.out.println("=== 错误率降低统计 ===");
int totalBefore = BEFORE_ERRORS.values().stream().mapToInt(Integer::intValue).sum();
int totalAfter = AFTER_ERRORS.values().stream().mapToInt(Integer::intValue).sum();
double reductionRate = (double)(totalBefore - totalAfter) / totalBefore * 100;
System.out.printf("总错误数:%d → %d%n", totalBefore, totalAfter);
System.out.printf("错误率降低:%.1f%%%n", reductionRate);
BEFORE_ERRORS.forEach((errorType, beforeCount) -> {
int afterCount = AFTER_ERRORS.getOrDefault(errorType, 0);
double reduction = (double)(beforeCount - afterCount) / beforeCount * 100;
System.out.printf("%s:%d → %d (降低 %.1f%%)%n",
errorType, beforeCount, afterCount, reduction);
});
}
}
🎓 成果总结
小李的技能提升
1. 技术能力提升
- 注解处理器理解:掌握了编译时代码生成的原理
- 映射模式:学会了对象映射的最佳实践
- 性能优化:理解了批量处理和内存优化技巧
- Spring 集成:熟练使用依赖注入和组件化开发
2. 开发效率提升
- 编码速度:映射代码编写时间减少 80%
- 调试时间:编译时错误检查,调试时间减少 70%
- 维护成本:代码可读性提升,维护时间减少 60%
3. 代码质量提升
- 类型安全:编译时类型检查,避免运行时错误
- 可读性:声明式映射规则,代码意图清晰
- 可测试性:映射逻辑独立,便于单元测试
团队收益
1. 开发规范统一
java
// 团队统一的映射器开发规范
@Mapper(
componentModel = "spring", // 统一使用 Spring 集成
unmappedTargetPolicy = ReportingPolicy.WARN, // 统一的未映射字段策略
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT
)
public interface StandardMapper {
// 统一的命名规范:to + 目标类型名
TargetDto toDto(Source source);
// 统一的批量映射
List<TargetDto> toDtos(List<Source> sources);
// 统一的自定义映射方法
@Named("customMapping")
default String customMapping(Object input) {
// 自定义逻辑
return "";
}
}
2. 技术债务减少
- 历史代码重构:逐步替换手动映射代码
- 性能优化:统一的性能优化策略
- 维护成本降低:标准化的开发模式
案例总结:通过 Atlas Mapper 的引入,小李不仅解决了订单系统的映射问题,还显著提升了开发效率和代码质量。这个案例展示了框架在实际业务场景中的强大能力和实用价值,为初级开发者提供了完整的学习和应用指南。