Search code examples
javaspring-data-jpamapstruct

Spring Data and JPA One to many with MapStruct


I have a one to many relation between Config and ConfigHeaders. Here is the Config mapper:

@Mapper(componentModel = "spring", uses = {UserMapper.class, ConfigHeadersMapper.class})
public interface ConfigMapper extends EntityMapper<ConfigDTO, Config> {

    @Mapping(source = "user.id", target = "userId")
    ConfigDTO toDto(Config config);

    @Mapping(source = "userId", target = "user")
    @Mapping(target = "messages", ignore = true)
    Config toEntity(ConfigDTO configDTO);

    default Config fromId(Long id) {
        if (id == null) {
            return null;
        }
        Config config = new Config();
        config.setId(id);
        return config;
    }
}

and here is the ConfigHeadersMapper:

@Mapper(componentModel = "spring", uses = {ConfigMapper.class})
public interface ConfigHeadersMapper extends EntityMapper<ConfigHeadersDTO, ConfigHeaders> {

    @Mapping(source = "config.id", target = "configId")
    ConfigHeadersDTO toDto(ConfigHeaders configHeaders);

    @Mapping(source = "configId", target = "config")
    ConfigHeaders toEntity(ConfigHeadersDTO configHeadersDTO);

    default ConfigHeaders fromId(Long id) {
        if (id == null) {
            return null;
        }
        ConfigHeaders configHeaders = new ConfigHeaders();
        configHeaders.setId(id);
        return configHeaders;
    }
}

When I try to save a new entity (ids equal to null for the Config & ConfigHeaders) this piece of code:

final Config config = configMapper.toEntity(configDTO);
Config newConfig = configRepository.save(config);

saves the Config and the ConfigHeaders but the config_id (FK) at the ConfigHeaders is NULL.

So I tried this code:

final Config config = configMapper.toEntity(configDTO);
config.getHeaders().stream().forEach(header -> header.setConfig(config));
Config newConfig = configRepository.save(config);

and indeed saves the children (ConfigHeaders) with the auto generated parent ID (config_id).

Could you tell me what am I doing wrong with the MapStruct? I am quite new with this tool and I do not believe this is the correct solution. I consider the previous code only a workaround for now.

Actually I have checked the MapStruct implementation code that have been generated and I have noted that it sets correctly the parent ID (that is null for new ones) but it does not set the backwards relationship with the father entity. How could I accomplish this through MapStruct?

Thank you in advance


Solution

  • As you have noticed in order JPA to perform the save correctly you need to set the link to the config in the ConfigHeader.

    There are 2 ways you can achieve this:

    First option:

    You can use CollectionMappingStrategy#ADDER_PREFERRED (see more here). For this you need to add in your Config something like:

    public void addHeader(ConfigHeader header) {
        this.headers.add(header);
        header.setConfig(this);
    }
    

    Second option:

    You use @AfterMapping in your ConfigMapper and set the config on all headers there.

    @AfterMapping
    default void linkHeaders(@MappingTarget Config config) {
        config.getHeaders().stream().forEach(header -> header.setConfig(config));
    }
    

    Third option as proposed by @sjaak in https://stackoverflow.com/a/48974119/1115491:

    You can use the @Context attribute as shown in the mapstruct-jpa-child-parent example. This has the same performance as the first option, as you don't have to iterate over the headers twice. The other benefit of this is that you can use it when you can't add stuff to your entities.

    It looks like:

    public class ConfigContext {
        private Config config;
        @BeforeMapping
        public void setConfig(@MappingTarget Config config) {
           this.config = config;
        }
    
        @AfterMapping
        public void establishRelation(@MappingTarget ConfigHeader header) {
            header.setConfig( header );
        }
    }
    

    You would also need to adapt your toEntity methods so they include the context as well.