Как сопоставить объект, содержащий другой объект, с помощью @OneToMany

#hibernate #spring-data-jpa #dto #mapstruct

Вопрос:

Учитывая исходный класс, как определено ниже:

 class Person{
    private String name;
    private int age;
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "peroson")
    List<Phone> phones
    // getters and setters
}
 

и класс телефона, как определено ниже:

 class Phone{
        private Long id;
        private String phoneNumber;
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "person")
        private Person person;
        // getters and setters
    }
 

и объекты DTO:

 class PersonDTO{
        private String name;
        private int age;
        List<PhoneDTO> phones
        // getters and setters
    }

class PhoneDTO{
            private Long id;
            private String phoneNumber;
            private PersonDTO person;
            // getters and setters
        }
 

У меня есть интерфейс :

 @Mapper(uses={PersonMapper.class})
interface PhoneMapper{
    PhoneDTO toDto(Phone source);
    Phone toEntity(PhoneDTO source);
}

 @Mapper(uses={PhoneMapper.class})
    interface PersonMapper{
        PersonDTO toDto(Person source);
        Person toEntity(PersonDTO source);
    }
 

когда я использую PeronMapper или PhoneMapper есть рекурсивный вызов, потому PersonMapper что использую PhoneMapper и PersonMapper использую PhoneMapper .

как это решить?

Ответ №1:

Существует множество способов избежать рекурсии.

Первый вариант:

Избегайте явного игнорирования в одном из ваших картографов

Второй вариант:

Используйте @Context и напишите свои собственные воспоминания. В репозитории примеров mapstruct есть пример mapstruct-сопоставление с циклами.

Идея состоит в том, чтобы иметь @Context

 public class CycleAvoidingMappingContext {
    private Map<Object, Object> knownInstances = new IdentityHashMap<Object, Object>();

    @BeforeMapping
    public <T> T getMappedInstance(Object source, @TargetType Class<T> targetType) {
        return (T) knownInstances.get( source );
    }

    @BeforeMapping
    public void storeMappedInstance(Object source, @MappingTarget Object target) {
        knownInstances.put( source, target );
    }
}
 

А затем передайте экземпляр этого контекста своим картографам:

 @Mapper(uses={PersonMapper.class})
interface PhoneMapper{
    PhoneDTO toDto(Phone source, @Context CycleAvoidingContext context);
    Phone toEntity(PhoneDTO source, @Context CycleAvoidingContext context);
}

@Mapper(uses={PhoneMapper.class})
interface PersonMapper{
    PersonDTO toDto(Person source, @Context CycleAvoidingContext context);
    Person toEntity(PersonDTO source, @Context CycleAvoidingContext context);
}
 

Конечно CycleAvoidingMappingContext , у них могут быть более конкретные источники, а не просто Object . У вас может быть один для Person и PersonDTO только для примера. От вас зависит, как вы хотите это реализовать.

Редактировать:

Когда у вас есть циклическая зависимость между сопоставителями, единственным решением является использование структуры внедрения зависимостей. Mappers Фабрика MapStruct по умолчанию не поддерживает циклы.

Комментарии:

1. Это решение для другой проблемы A -> B ->> A. Здесь проблема заключается в экземпляре картографа. Amapper вызывает Bmapper, но Bmapper также использует Amapper => рекурсивное использование. У меня та же проблема, но я не знаю, как ее решить

2. Вы правы @Sum_Pie. Я добавил правку в вопрос, чтобы уточнить, что

3. Хорошо, на самом деле сегодня я решил проблему таким образом: @Mapper(uses=B_Mapper.class) A_Mapper { } interface B_Mapper{ A_Mapper mapA = Mappers.get(A_Mapper.class) }