如何映射父类受保护属性?MapStruct优化实现问询
现有一套Java类结构,包含审计字段父类EntityMetaData、继承它的实体类DeliveryPartnerCredentials,以及对应的DTO类DPCModel。现在用MapStruct做实体和DTO的映射,但遇到两个麻烦:
- 必须给
EntityMetaData的protected属性加Getter/Setter,MapStruct才能访问 - 大量实体都继承了
EntityMetaData,每个实体的Mapper都要重复写四个审计字段的@Mapping,太繁琐
相关代码如下:
实体与父类代码
@Getter @Setter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public class EntityMetaData { protected Integer createdBy; @CreatedDate protected Instant createdAt; protected Integer updatedBy; @LastModifiedDate protected Instant updatedAt; } @Entity @Getter @Setter @NoArgsConstructor public class DeliveryPartnerCredentials extends EntityMetaData { @Id private Long id; private Integer companyNo; @Enumerated(EnumType.STRING) private DeliveryPartner deliveryPartner; @ElementCollection(fetch = FetchType.EAGER) private Map<String, String> credentials = new HashMap<>(); @Enumerated(EnumType.STRING) private EntityStatus status = EntityStatus.ACTIVE; }
DTO代码
@Getter @Setter @NoArgsConstructor public class DPCModel { private Long id; private Integer companyNo; private DeliveryPartner deliveryPartner; private Map<String, String> credentials; private EntityStatus status; private Integer createdBy; private Instant createdAt; private Integer updatedBy; private Instant updatedAt; }
当前的Mapper实现
@Mapper public interface CredentialsMapper { @Mapping(target = "createdBy", expression = "java(deliveryPartnerCredentials.getCreatedBy())") @Mapping(target = "createdAt", expression = "java(deliveryPartnerCredentials.getCreatedAt())") @Mapping(target = "updatedBy", expression = "java(deliveryPartnerCredentials.getUpdatedBy())") @Mapping(target = "updatedAt", expression = "java(deliveryPartnerCredentials.getUpdatedAt())") DPCModel entityToModel(DeliveryPartnerCredentials deliveryPartnerCredentials); DeliveryPartnerCredentials modelToEntity(DPCModel dpcModel); }
方案1:用MapStruct的@MapperConfig复用映射规则,直接访问父类字段
MapStruct支持用@MapperConfig定义通用映射规则,让所有继承EntityMetaData的Mapper都复用这套规则,而且因为子类能直接访问父类的protected字段,根本不需要给EntityMetaData加Getter/Setter。
第一步:抽审计字段的DTO父类
先把所有DTO里的审计字段抽成一个父类,避免重复定义:
@Getter @Setter @NoArgsConstructor public class BaseAuditModel { private Integer createdBy; private Instant createdAt; private Integer updatedBy; private Instant updatedAt; } // 修改DPCModel继承这个父类 @Getter @Setter @NoArgsConstructor public class DPCModel extends BaseAuditModel { private Long id; private Integer companyNo; private DeliveryPartner deliveryPartner; private Map<String, String> credentials; private EntityStatus status; }
第二步:定义通用映射配置
@MapperConfig public interface BaseAuditMappingConfig { // 定义审计字段的通用映射规则 @Mapping(target = "createdBy", source = "createdBy") @Mapping(target = "createdAt", source = "createdAt") @Mapping(target = "updatedBy", source = "updatedBy") @Mapping(target = "updatedAt", source = "updatedAt") <T extends EntityMetaData, U extends BaseAuditModel> U mapAuditFields(T entity, U dto); }
第三步:在具体Mapper中继承配置
现在你的CredentialsMapper可以简化成这样,不需要再写重复的@Mapping:
@Mapper(config = BaseAuditMappingConfig.class) public interface CredentialsMapper { DPCModel entityToModel(DeliveryPartnerCredentials deliveryPartnerCredentials); DeliveryPartnerCredentials modelToEntity(DPCModel dpcModel); }
MapStruct生成代码时,会直接通过entity.createdBy这种方式访问父类的protected字段,完全不需要Getter。
方案2:用抽象Mapper封装通用逻辑
如果不想用@MapperConfig,可以写一个抽象Mapper类,把审计字段的映射逻辑封装进去,其他Mapper继承这个抽象类就行:
第一步:写抽象父类Mapper
public abstract class BaseAuditMapper<T extends EntityMetaData, U extends BaseAuditModel> { public U entityToModel(T entity) { U dto = mapEntitySpecificFields(entity); // 直接访问父类的protected字段赋值 dto.setCreatedBy(entity.createdBy); dto.setCreatedAt(entity.createdAt); dto.setUpdatedBy(entity.updatedBy); dto.setUpdatedAt(entity.updatedAt); return dto; } public T modelToEntity(U dto) { T entity = mapDtoSpecificFields(dto); // 反向赋值,同样直接访问字段 entity.createdBy = dto.getCreatedBy(); entity.createdAt = dto.getCreatedAt(); entity.updatedBy = dto.getUpdatedBy(); entity.updatedAt = dto.getUpdatedAt(); return entity; } // 子类只需要实现自己特有的字段映射 protected abstract U mapEntitySpecificFields(T entity); protected abstract T mapDtoSpecificFields(U dto); }
第二步:具体Mapper继承抽象类
@Mapper public abstract class CredentialsMapper extends BaseAuditMapper<DeliveryPartnerCredentials, DPCModel> { @Override protected DPCModel mapEntitySpecificFields(DeliveryPartnerCredentials entity) { DPCModel dto = new DPCModel(); dto.setId(entity.getId()); dto.setCompanyNo(entity.getCompanyNo()); dto.setDeliveryPartner(entity.getDeliveryPartner()); dto.setCredentials(entity.getCredentials()); dto.setStatus(entity.getStatus()); return dto; } @Override protected DeliveryPartnerCredentials mapDtoSpecificFields(DPCModel dto) { DeliveryPartnerCredentials entity = new DeliveryPartnerCredentials(); entity.setId(dto.getId()); entity.setCompanyNo(dto.getCompanyNo()); entity.setDeliveryPartner(dto.getDeliveryPartner()); entity.setCredentials(dto.getCredentials()); entity.setStatus(dto.getStatus()); return entity; } }
这种方式完全不需要给EntityMetaData加任何Getter/Setter,通用逻辑都在抽象类里,子类只处理自己的字段,非常清爽。
方案3:让MapStruct自动匹配同名字段(最简单)
其实如果你的实体和DTO的审计字段名称完全一致,MapStruct可以自动匹配这些字段,而且因为子类能访问父类的protected字段,直接就能映射,连额外配置都不需要。
直接把Mapper改成这样:
@Mapper public interface CredentialsMapper { // 啥额外@Mapping都不用加,MapStruct自动匹配所有同名字段,包括父类的protected字段 DPCModel entityToModel(DeliveryPartnerCredentials deliveryPartnerCredentials); DeliveryPartnerCredentials modelToEntity(DPCModel dpcModel); }
前提是EntityMetaData和子类在同一个包下,或者子类能访问到父类的protected字段(Java的访问权限规则),MapStruct生成的代码会直接用entity.createdBy来赋值,不需要Getter。
- Java里子类本来就可以直接访问父类的protected字段(只要符合访问权限规则),所以MapStruct生成的代码也能直接操作这些字段,根本不需要Getter/Setter。
- 不管用
@MapperConfig还是抽象类,都是把重复的审计字段映射逻辑抽出来复用,避免每个Mapper都写一遍,减少冗余。
内容的提问来源于stack exchange,提问作者Keshavram Kuduwa

