Mapstruct, resolving Spring mapper dependency cycle

52 views Asked by At

I've got the following entity objects:

public abstract class AssociatedAccount {
    // ...
    @OneToMany(
        mappedBy = "associatedAccount"
    )
    private Set<ContactLocationRelation> contactLocationRelations;
    // ...
}

public class ContactLocationRelation {
    // ...
    @ManyToOne()
    private AssociatedAccount associatedAccount;
    // ...
}

And the following DTO's

public abstract class AssociatedAccountDTO {
    // ...
    private Set<ContactLocationRelationDTO> contactLocationRelationDTOList;
    // ...
}

public class ContactLocationRelationDTO {
    // ...
    private AssociatedAccountDTO associatedAccountDTO;
    // ...
}

I've made their mappers

@Mapper(
    componentModel = "spring", 
    subclassExhaustiveStrategy = SubclassExhaustiveStrategy.RUNTIME_EXCEPTION,
    uses = {
        CategoryMapper.class,
        NoteMapper.class, 
        PaymentTermMapper.class,
        PriceLevelMapper.class,
        LateFeeMapper.class,
        ContactLocationRelationMapper.class
    }
)
public interface AssociatedAccountMapper {
    
    @SubclassMapping( source = CustomerAccount.class, target = CustomerAccountDTO.class )
    @SubclassMapping( source = CompanyAccount.class, target = CompanyAccountDTO.class )
    //...
    @Mapping( source = "contactLocationRelations", target = "contactLocationRelationDTOList" )
    AssociatedAccountDTO toDTO(AssociatedAccount source, @Context CycleAvoidingMappingContext context);

    @InheritInverseConfiguration(name = "toDTO")
    AssociatedAccount toEntity(AssociatedAccountDTO source, @Context CycleAvoidingMappingContext context); 

    @InheritConfiguration(name = "toDTO")
    //...
    public CustomerAccountDTO customerAccountToDTO(CustomerAccount source, @Context CycleAvoidingMappingContext context);

    @InheritInverseConfiguration(name = "customerAccountToDTO")
    public CustomerAccount customerDTOToEntity(CustomerAccountDTO source, @Context CycleAvoidingMappingContext context);

    @InheritConfiguration(name = "toDTO")
    //...
    public CompanyAccountDTO companyAccountToDTO(CompanyAccount source, @Context CycleAvoidingMappingContext context);

    @InheritInverseConfiguration(name = "companyAccountToDTO")
    public CompanyAccount companyAccountDTOToEntity(CompanyAccountDTO source, @Context CycleAvoidingMappingContext context);
}

@Mapper(
    componentModel = "spring", 
    uses = {
        AssociatedAccountMapper.class,
        LocationMapper.class, 
        ContactMapper.class
    }
)

And the other mapper

public interface ContactLocationRelationMapper {
    
    @Mapping( source = "associatedAccount", target = "associatedAccountDTO" )
    //...
    ContactLocationRelationDTO toDTO(ContactLocationRelation source, @Context CycleAvoidingMappingContext context);

    @InheritInverseConfiguration(name = "toDTO")
    ContactLocationRelation toEntity(ContactLocationRelationDTO source, @Context CycleAvoidingMappingContext context);
}

With this setup I am getting a spring dependency cycle issue


APPLICATION FAILED TO START


Description:

The dependencies of some of the beans in the application context form a cycle:

??????? | associatedAccountMapperImpl (field private com.server.springboot.mapping.global.ContactLocationRelationMapper com.server.springboot.mapping.customers.AssociatedAccountMapperImpl.contactLocationRelationMapper) ? ? | contactLocationRelationMapperImpl defined in file [C:...] ???????

Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

So I search the web and documentation and all I found was this text.

So I attempted to follow their guide (as demonstrated in my mapper class methods having the context parameter) and I still got the same error.

I'd like to point out a few things - their example is quite simple having an entitydto pair that relies on itself (being the Employee class).

In my case I've got two seperate entitydto pairs whose mappers rely on each other.

More specifically in the @Mapper annotatation, the "uses" parameter includes the opposing mapper class. In addition, the component = "spring" is instructing mapstruct to make both of these mappers autowired in their counterpart class thus resulting in the dependency issue that doesn't exist in the example.

Note this is what mapstruct autogenerates

@Component
public class AssociatedAccountMapperImpl implements AssociatedAccountMapper {
    
    //...
    @Autowired
    private ContactLocationRelationMapper contactLocationRelationMapper;
    //...
}

and for the counterpart, this is what mapstruct autogenerates

@Component
public class ContactLocationRelationMapperImpl implements ContactLocationRelationMapper {

    //...
    @Autowired
    private AssociatedAccountMapper associatedAccountMapper;
    //...
}

I have reason to believe that its these @Autowired fields that are causing the issue as they don't exist in the example solution. I am unable to remove these @Autowired fields because mapstruct suggests its best practice to use Ioc if youre using spring (in this case I am) and because the uses = attribute in @Mapper instructs mapstruct to use the methods from one mapper class in the other without having to reconfigure the same mapping in the counterpart class.

So I am stuck in my own cycle and am not sure how to resolve these issues. Any suggestions?

0

There are 0 answers