mapstruct-patterns
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMapStruct Patterns for Jakarta EE
适用于Jakarta EE的MapStruct模式
Best practices for using MapStruct with constructor-based mapping to achieve compile-time safety. When constructors change, mappings fail to compile — no runtime surprises.
使用MapStruct进行基于构造函数的映射以实现编译时安全性的最佳实践。当构造函数发生变更时,映射会编译失败——不会出现运行时意外问题。
Core Philosophy
核心理念
Use constructors, not setters. This gives you compile-time errors when fields change.
Records naturally enforce this. For mutable entities, use the annotation.
@Default使用构造函数,而非setter方法。这样当字段发生变更时,你会得到编译时错误。
Records(Java记录类)天然支持这一点。对于可变实体,可使用注解。
@DefaultCDI Setup
CDI配置
java
@Mapper(componentModel = "cdi") // CDI injection
public interface OrderMapper {
OrderResponse toResponse(Order order);
}java
@Mapper(componentModel = "cdi") // CDI注入
public interface OrderMapper {
OrderResponse toResponse(Order order);
}The @Default Annotation Trick
@Default注解技巧
MapStruct uses any annotation named to select the constructor. Create your own:
@Defaultjava
package com.example.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.CLASS)
public @interface Default {
}MapStruct会使用任何名为的注解来选择构造函数。你可以自定义一个:
@Defaultjava
package com.example.mapstruct;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.CLASS)
public @interface Default {
}Usage on Mutable Entities
在可变实体上的使用
java
@Entity
public class Order {
@Id @GeneratedValue
private Long id;
private String customerId;
private BigDecimal total;
private OrderStatus status;
// JPA needs this
protected Order() {}
// MapStruct uses this - CHANGE HERE = COMPILER ERROR in mapper
@Default
public Order(String customerId, BigDecimal total, OrderStatus status) {
this.customerId = customerId;
this.total = total;
this.status = status;
}
}java
@Entity
public class Order {
@Id @GeneratedValue
private Long id;
private String customerId;
private BigDecimal total;
private OrderStatus status;
// JPA需要此无参构造函数
protected Order() {}
// MapStruct会使用此构造函数——此处变更会导致映射器编译错误
@Default
public Order(String customerId, BigDecimal total, OrderStatus status) {
this.customerId = customerId;
this.total = total;
this.status = status;
}
}Records (Ideal Case)
Records(理想场景)
Records automatically work with constructor mapping:
java
// No @Default needed - single constructor
public record OrderResponse(
String orderId,
String customerId,
String total,
String status
) {}
@Mapper(componentModel = "cdi")
public interface OrderMapper {
@Mapping(target = "orderId", source = "id")
@Mapping(target = "total", expression = "java(order.getTotal().toString())")
OrderResponse toResponse(Order order);
}Records会自动支持构造函数映射:
java
// 无需@Default注解——单个构造函数即可
public record OrderResponse(
String orderId,
String customerId,
String total,
String status
) {}
@Mapper(componentModel = "cdi")
public interface OrderMapper {
@Mapping(target = "orderId", source = "id")
@Mapping(target = "total", expression = "java(order.getTotal().toString())")
OrderResponse toResponse(Order order);
}Key Patterns
关键模式
1. Constructor-Based Mapping
1. 基于构造函数的映射
java
@Mapper(componentModel = "cdi")
public interface CustomerMapper {
// MapStruct uses Customer constructor, fail if signature changes
Customer toEntity(CreateCustomerRequest request);
// MapStruct uses CustomerResponse constructor
CustomerResponse toResponse(Customer customer);
}java
@Mapper(componentModel = "cdi")
public interface CustomerMapper {
// MapStruct会使用Customer的构造函数,若签名变更则会失败
Customer toEntity(CreateCustomerRequest request);
// MapStruct会使用CustomerResponse的构造函数
CustomerResponse toResponse(Customer customer);
}2. Custom @Default for Entities
2. 为实体自定义@Default注解
java
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
private BigDecimal price;
private String category;
protected Product() {}
@Default // Your custom annotation
public Product(String name, BigDecimal price, String category) {
this.name = name;
this.price = price;
this.category = category;
}
}java
@Entity
public class Product {
@Id @GeneratedValue
private Long id;
private String name;
private BigDecimal price;
private String category;
protected Product() {}
@Default // 你的自定义注解
public Product(String name, BigDecimal price, String category) {
this.name = name;
this.price = price;
this.category = category;
}
}Anti-Pattern: Setter-Based Mapping
反模式:基于Setter的映射
java
// ❌ Can add field to DTO, forget mapper, get null at runtime
public class OrderDTO {
private String id;
private String status;
private String newField; // Added later, no error!
// Just setters...
}
// ✓ Add field to constructor = compiler error in mapper
public record OrderDTO(String id, String status, String newField) {}java
// ❌ 可为DTO添加字段,却忘记更新映射器,导致运行时出现null值
public class OrderDTO {
private String id;
private String status;
private String newField; // 后续添加的字段,无错误提示!
// 仅包含setter方法...
}
// ✓ 向构造函数添加字段会导致映射器编译错误
public record OrderDTO(String id, String status, String newField) {}Compile-Time Safety Benefit
编译时安全性的优势
java
// Before: Record has 3 fields
public record OrderResponse(String id, String status, String total) {}
// After: Added customerName field
public record OrderResponse(String id, String status, String total, String customerName) {}
// Mapper now FAILS TO COMPILE until you add the mapping:
@Mapper(componentModel = "cdi")
public interface OrderMapper {
@Mapping(target = "customerName", source = "customer.name") // Must add this
OrderResponse toResponse(Order order);
}java
// 之前:Record包含3个字段
public record OrderResponse(String id, String status, String total) {}
// 之后:添加了customerName字段
public record OrderResponse(String id, String status, String total, String customerName) {}
// 此时映射器会编译失败,直到你添加对应的映射配置:
@Mapper(componentModel = "cdi")
public interface OrderMapper {
@Mapping(target = "customerName", source = "customer.name") // 必须添加此配置
OrderResponse toResponse(Order order);
}Cookbook Index
指南索引
Setup & Configuration
配置与设置
- cdi-setup - CDI/MicroProfile setup
- default-annotation - Custom @Default annotation
- cdi-setup - CDI/MicroProfile配置
- default-annotation - 自定义@Default注解
Mapping Patterns
映射模式
- constructor-mapping - Constructor-based mapping
- record-mapping - Java Records mapping
- entity-mapping - JPA entity mapping
- constructor-mapping - 基于构造函数的映射
- record-mapping - Java Records映射
- entity-mapping - JPA实体映射