How to map bi-directional dependencies?

1,241 views
Skip to first unread message

George Pai

unread,
Apr 29, 2016, 10:29:30 AM4/29/16
to mapstruct-users
Using version 1.1.0.Beta1

So I'm trying to map a couple of objects with bidirectional dependencies (and thus we have a graph instead of a tree).  The naive implementation causes a stack overflow since the mappers just constantly bounce back and forth between the objects, and I'm not seeing anything in the documentation that seems to cover this or offer a solution.

public class AccountEntity {
   
private Set<ContractEntity> contracts;
}


public class ContractEntity {
   
private AccountEntity account;
}


public class Account {
   
private Set<Contract> contracts;
}


public class Contract {
   
private Account account;
}


@Mapper(uses = ContractMapper.class)
public interface AccountMapper {
   
Account toAccount(AccountEntity accountEntity);

   
AccountEntity toAccountEntity(Account account);

   
Set<Account> toAccounts(Collection<AccountEntity> accountEntities);

   
Set<AccountEntity> toAccountEntities(Collection<Account> accounts);
}


@Mapper(uses = AccountMapper.class)
public interface ContractMapper {
   
Contract toContract(ContractEntity contractEntity);

   
ContractEntity toContractEntity(Contract contract);

   
Set<Contract> toContracts(Collection<ContractEntity> contractEntities);

   
Set<ContractEntity> toContractEntities(Collection<Contract> contracts);
}

Ideally the dependency would be kept after the mapping (the contract.account object instance is the same object instance whose account.contracts holds that contract) however I would take a solution that prevents the stack overflow.

Andreas Gudian

unread,
Apr 29, 2016, 5:36:43 PM4/29/16
to George Pai, mapstruct-users
Hi George,

we didn't yet implement this, but we have it on the radar: https://github.com/mapstruct/mapstruct/issues/469

I just added a small example how a workaround could look like. Basically you'd exclude the reference to the other type using @Mapping(target = .., ignore=true) and populate the references manually in @AfterMapping methods.

Hope that helps - if not, let us know.

Thanks,
Andreas



--
You received this message because you are subscribed to the Google Groups "mapstruct-users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to mapstruct-use...@googlegroups.com.
To post to this group, send email to mapstru...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

George Pai

unread,
May 2, 2016, 11:52:47 AM5/2/16
to mapstruct-users, geo...@megalobrainiac.com
It sort of works, calling toAccount works fine with the @AfterMapping, however it does not when mapping contracts.

Calling contractMapper.toContract(contractEntity) from my service just leaves account empty, because it never calls toAccount due to the ignore and therefore does not call populateAccount afterwards.  And I can't create an @AfterMapping in ContractMapper because I have no reference to account in contract to populate it with.


public class AccountEntity {
   
private String id;

   
private Set<ContractEntity> contracts;
}

public class ContractEntity {

   
private String id;

   
private AccountEntity account;
}

public class Account {

   
private String id;

   
private Set<Contract> contracts;
}

public class Contract {

   
private String id;

   
private Account account;
}

@Mapper(uses = ContractMapper.class)
public abstract class AccountMapper {

   
@AfterMapping
   
protected void populateAccount(@MappingTarget Account account) {
        account
.getContracts().forEach(contract -> contract.setAccount(account));
   
}

   
@AfterMapping
   
protected void populateAccount(@MappingTarget AccountEntity accountEntity) {
        accountEntity
.getContracts().forEach(contractEntity -> contractEntity.setAccount(accountEntity));
   
}

   
public abstract Account toAccount(AccountEntity accountEntity);

   
public abstract AccountEntity toAccountEntity(Account account);

   
public abstract Set<Account> toAccounts(Collection<AccountEntity> accountEntities);

   
public abstract Set<AccountEntity> toAccountEntities(Collection<Account> accounts);

}


@Mapper(uses = AccountMapper.class)
public interface ContractMapper {

    
@Mapping(target = "account", ignore = true)
    Contract toContract(ContractEntity contractEntity);

   
@Mapping(target = "account", ignore = true)
Reply all
Reply to author
Forward
0 new messages