springspring-bootmapstruct

How to save a onetomany with org.mapstruct.Mapper?


I'm developing a spring boot application for now. Now I have a problem with mapping with org.mapstruct.Mapper.

For short I have an entity:

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;

}


@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<>();

}

There is a controller which creates an address. For that a DTO was created with :

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;
}

To display CreateAddressDTO.userId in swagger as long I specified it as a long.

To map DTO to entity mapper was created:

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);

}

user is not being passed from frontend so it is ignored.

Then at last in service:

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;
    }

}

userId is passed as a long. It is being processed on its own. First get via UserRepository and then attached to address.

I send data via endpoint:

{
  "locality": "string",
  "street": "string",
  "house": "string",
  "porch": "string",
  "floor": "string",
  "apartment": "string",
  "userId": 1
}

So my question is: is that the way foreign keys handled in cases like mine?


Solution

  • Through using MapStruct, you can define fetchUser method as default one to handle with toEntity process

    @Mapper(componentModel = "spring",  
            uses = UserRepository.class)
    public interface AddressMapper {
      
      @Mapping(target = "user",
               expression = "java(fetchUser(dto.getUserId(), userRepository))")
      Address toEntity(CreateAddressDTO dto, @Context UserRepository userRepository);
    
      default User fetchUser(Long id, UserRepository repo) {
        return repo.findById(id)
                   .orElseThrow(() -> new EntityNotFoundException("User not found: " + id));
      }
    }
    

    Next, you can add these code block shown below to relevant service.

    Address address = addressMapper.toEntity(dto, userRepository);
    addressRepo.save(address);
    

    I hope you can help you fix your issue.