Spring Data和JPA一对多使用MapStruct

时间:2018-02-24 20:19:17

标签: spring-data-jpa mapstruct

我在Config和ConfigHeaders之间有一对多的关系。 这是Config映射器:

  pg_ctl status 

这是ConfigHeadersMapper:

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

当我尝试保存一个新实体(ID为Config&amp; ConfigHeaders的等于null)时,这段代码:

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

保存Config和ConfigHeaders,但ConfigHeaders的config_id(FK)为NULL。

所以我尝试了这段代码:

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

确实使用自动生成的父ID(config_id)保存子项(ConfigHeaders)。

你能告诉我MapStruct的错误吗? 我对这个工具很新,我不相信这是正确的解决方案。我认为以前的代码现在只是一种解决方法。

实际上我已经检查了已经生成的MapStruct实现代码,并且我已经注意到它正确设置了父ID(对于新ID是空的)但是它没有设置与父实体的向后关系。我怎么能通过MapStruct实现这个目标?

提前谢谢

2 个答案:

答案 0 :(得分:3)

你也可以使用@Context。这也为您提供了使用EntityManager进行操作的可能性。请查看here示例。

答案 1 :(得分:2)

正如您已注意到,为了让JPA正确执行保存,您需要在ConfigHeader中设置指向配置的链接。

有两种方法可以实现这一目标:

第一个选项:

您可以使用CollectionMappingStrategy#ADDER_PREFERRED(请参阅更多here)。为此,您需要添加Config之类的内容:

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

第二个选项:

您在@AfterMapping中使用ConfigMapper并在那里设置所有标题的配置。

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

@sjaak在https://stackoverflow.com/a/48974119/1115491中提出的第三个选项:

您可以使用mapstruct-jpa-child-parent示例中显示的@Context属性。这与第一个选项具有相同的性能,因为您不必遍历标题两次。这样做的另一个好处是,当您无法向实体添加内容时,可以使用它。

看起来像:

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

您还需要调整toEntity方法,以便它们也包含上下文。