You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何映射父类受保护属性?MapStruct优化实现问询

问题描述

现有一套Java类结构,包含审计字段父类EntityMetaData、继承它的实体类DeliveryPartnerCredentials,以及对应的DTO类DPCModel。现在用MapStruct做实体和DTO的映射,但遇到两个麻烦:

  1. 必须给EntityMetaData的protected属性加Getter/Setter,MapStruct才能访问
  2. 大量实体都继承了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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 15:35:36