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

MapStruct映射实体至DTO时UUID字段返回null问题求助

MapStruct映射问题:Transaction实体转TransactionResponse时sender/receiver字段为null

我有以下JPA实体:

@Data
@Entity
@Table(name="transactions")
public class Transaction {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(name = "transaction_uuid")
    public UUID transactionUUID;

    public BigDecimal amount;

    @ManyToOne
    public User senderUser;

    @ManyToOne
    public User receiverUser;
}

以及User类:

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.UUID)
    @Column(name = "user_uuid")
    public UUID userUUID;
    public String username;
    @Column(unique = true)
    public String email;
    @Column(unique = true)
    public String cpf;
    public String password;
    public BigDecimal balance;
    public UserType userType;
}

我尝试将其映射至如下DTO:

public record TransactionResponse(BigDecimal amount, UUID sender, UUID receiver) {
}

我的Mapper定义如下:

@Mapper(componentModel = "spring", uses = UserMapper.class)
public interface TransactionMapper {

    Transaction mapRequestToTransaction(TransactionRequest transactionRequest);

    // TODO: sender and receiver in target class are null
    @Mappings({
        @Mapping(target = "sender", source = "senderUser.userUUID"),
        @Mapping(target = "receiver", source = "receiverUser.userUUID")
    })
//    @Mapping(target = "sender", source = "senderUser", qualifiedByName = "sender")
//    @Mapping(target = "receiver", source = "receiverUser", qualifiedByName = "receiver")
    List<TransactionResponse> mapEntityListToTransactionResponseList(List<Transaction> transaction);

    @Named("sender")
    default UUID sender(Transaction item) {
        return item.getSenderUser().userUUID;
    }

    @Named("receiver")
    default UUID receiver(Transaction item) {
        return item.getReceiverUser().userUUID;
    }
}

注:注释部分为我已尝试但未成功的方案。

目标DTO中的sender和receiver字段始终返回null,以下是生成的实现类:

public class TransactionMapperImpl implements TransactionMapper {

    @Override
    public Transaction mapRequestToTransaction(TransactionRequest transactionRequest) {
        if ( transactionRequest == null ) {
            return null;
        }

        Transaction transaction = new Transaction();

        transaction.setAmount( transactionRequest.amount() );

        return transaction;
    }

    @Override
    public List<TransactionResponse> mapEntityListToTransactionResponseList(List<Transaction> transaction) {
        if ( transaction == null ) {
            return null;
        }

        List<TransactionResponse> list = new ArrayList<TransactionResponse>( transaction.size() );
        for ( Transaction transaction1 : transaction ) {
            list.add( transactionToTransactionResponse( transaction1 ) );
        }

        return list;
    }

    protected TransactionResponse transactionToTransactionResponse(Transaction transaction) {
        if ( transaction == null ) {
            return null;
        }

        BigDecimal amount = null;

        amount = transaction.getAmount();

        UUID sender = null;
        UUID receiver = null;

        TransactionResponse transactionResponse = new TransactionResponse( amount, sender, receiver );

        return transactionResponse;
    }
}

我已尝试的方案:
1.

@Mapping(target = "sender", source = "senderUser.userUUID"),
@Mapping(target = "receiver", source = "receiverUser.userUUID")
@Mapping(target = "sender", source = "transaction.senderUser.userUUID"),
@Mapping(target = "receiver", source = "transaction.receiverUser.userUUID")
@Mapping(target = "sender", source = "senderUser", qualifiedByName = "sender")
@Mapping(target = "receiver", source = "receiverUser", qualifiedByName = "receiver")

@Named("sender")
default UUID sender(Transaction item) {
    return item.getSenderUser().userUUID;
}

@Named("receiver")
default UUID receiver(Transaction item) {
    return item.getReceiverUser().userUUID;
}

问题原因与解决办法

核心问题是:MapStruct的映射注解没有绑定到单个实体转DTO的处理逻辑上。你只给List转换方法加了@Mapping,但MapStruct生成代码时,是通过内部的transactionToTransactionResponse方法处理单个实体转换,这个方法没有拿到映射规则。

正确的Mapper写法

需要把映射注解加到单个实体转DTO的方法上,List转换会自动复用这个方法的规则:

@Mapper(componentModel = "spring", uses = UserMapper.class)
public interface TransactionMapper {

    Transaction mapRequestToTransaction(TransactionRequest transactionRequest);

    // 给单个实体转DTO的方法添加映射规则
    @Mappings({
        @Mapping(target = "sender", source = "senderUser.userUUID"),
        @Mapping(target = "receiver", source = "receiverUser.userUUID")
    })
    TransactionResponse mapTransactionToResponse(Transaction transaction);

    // List转换无需额外加注解,自动调用上面的单个转换方法
    List<TransactionResponse> mapEntityListToTransactionResponseList(List<Transaction> transactions);
}

为什么之前的方案无效?

  1. 直接给List转换方法加@Mapping:MapStruct不会自动将这些规则传递给内部生成的单个实体转换方法,必须显式定义单个转换方法并绑定注解。
  2. 使用@Named的方案:你的@Named方法参数是Transaction,但映射时source指定的是senderUser(User类型),参数类型不匹配,MapStruct无法调用这些默认方法。如果要使用@Named,应该改成接收User类型参数:
@Named("userToUuid")
default UUID userToUuid(User user) {
    return user != null ? user.userUUID : null;
}

// 映射时指定使用该方法
@Mapping(target = "sender", source = "senderUser", qualifiedByName = "userToUuid")
@Mapping(target = "receiver", source = "receiverUser", qualifiedByName = "userToUuid")
TransactionResponse mapTransactionToResponse(Transaction transaction);

额外注意事项

  • 确保JPA查询Transaction时,已关联查询senderUser和receiverUser(比如用JOIN FETCH或EntityGraph),否则关联对象本身为null,映射出的UUID自然也是null。
  • 实体类字段为public时MapStruct可直接访问,若改为private字段,需确保有对应的getter方法(或使用Lombok的@Data注解)。

内容的提问来源于stack exchange,提问作者fmassaretto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 21:53:23