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); }
为什么之前的方案无效?
- 直接给List转换方法加@Mapping:MapStruct不会自动将这些规则传递给内部生成的单个实体转换方法,必须显式定义单个转换方法并绑定注解。
- 使用@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
相关产品推荐
相关产品推荐

