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

使用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,提问作者Альберт Александров

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:48:19