使用org.mapstruct.Mapper处理一对多关联外键的方式是否合规?
Spring Boot中使用MapStruct处理关联实体外键的方式是否合理?
问题描述
我正在开发Spring Boot应用,使用org.mapstruct.Mapper进行对象映射时遇到疑问。现有User和Address一对多关联实体,创建地址时通过CreateAddressDTO传递userId,Mapper中忽略user字段,在Service层通过userId查询User实例后设置到Address再保存,想咨询这种处理外键的方式是否正确。
相关代码
Address实体
// Address实体 package ru.innopolis.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Table(name = "addresses") public class Address { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String locality; private String street; private String house; private String porch; private String floor; private String apartment; @ManyToOne @JoinColumn(name = "user_id", nullable = false) private User user; }
User实体
// User实体 @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "first_name", nullable = false) private String firstName; @Column(name = "last_name", nullable = false) private String lastName; @Column(name = "phone_number", nullable = false) private String phoneNumber; @OneToMany(mappedBy = "user") private List<Order> orders = new ArrayList<>(); }
CreateAddressDTO
// CreateAddressDTO package ru.innopolis.dto; import lombok.Getter; import lombok.Setter; @Getter @Setter public class CreateAddressDTO { public String locality; public String street; public String house; public String porch; public String floor; public String apartment; public Long userId; }
AddressMapper
// AddressMapper package ru.innopolis.mappers; import org.mapstruct.*; import ru.innopolis.dto.CreateAddressDTO; import ru.innopolis.dto.RetrieveAddressDTO; import ru.innopolis.entity.Address; import java.util.List; @Mapper( componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE, nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE ) public interface AddressMapper { @Mapping(target = "user", ignore = true) Address map(CreateAddressDTO data); @Mapping(source = "user.id", target = "userId") RetrieveAddressDTO map(Address address); }
AddressServiceImpl
// AddressServiceImpl package ru.innopolis.service; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import ru.innopolis.dto.CreateAddressDTO; import ru.innopolis.entity.Address; import ru.innopolis.mappers.AddressMapper; import ru.innopolis.repository.AddressRepository; import ru.innopolis.repository.UserRepository; import java.util.List; @Service @RequiredArgsConstructor public class AddressServiceImpl implements AddressService { private final AddressRepository addressRepository; private final UserRepository userRepository; private final AddressMapper addressMapper; @Override public Address createAddress(CreateAddressDTO data) { var address = addressMapper.map(data); var user = userRepository.findById(data.getUserId()).orElse(null); address.setUser(user); addressRepository.save(address); return address; } }
请求示例
{ "locality": "string", "street": "string", "house": "string", "porch": "string", "floor": "string", "apartment": "string", "userId": 1 }
回答
这种处理方式是合理且符合工程实践的,核心原因如下:
- 职责边界清晰:Mapper只负责纯数据字段的映射,不介入业务逻辑或数据库操作;外键关联这类需要依赖仓储查询的操作放在Service层处理,符合单一职责原则,代码耦合度更低。
- 避免映射层依赖仓储:如果强行让MapStruct直接处理User查询,会导致Mapper依赖UserRepository,破坏了映射层的纯粹性,后续维护成本更高。
- 异常处理更可控:当前代码中用
orElse(null)处理用户不存在的情况,实际会触发JPA的非空约束异常,建议改成主动抛出业务异常,在Service层提前拦截并返回友好错误:
var user = userRepository.findById(data.getUserId()) .orElseThrow(() -> new IllegalArgumentException("指定用户不存在"));
如果想进一步优化,也可以考虑用MapStruct的@Context传递已查询到的User实例,但这种方式并没有显著优势,当前的Service处理逻辑已经足够简洁清晰。
内容的提问来源于stack exchange,提问作者Альберт Александров
相关产品推荐
相关产品推荐

