bidirectional relationships in my domain model cause stack overflow due to perpetuity

173 views
Skip to first unread message

Denis Pavlov

unread,
May 21, 2013, 9:03:21 AM5/21/13
to geda-generic-dto-asse...@googlegroups.com
From email:

I have been setting up and testing GeDA as DTO mapping tool for our application and it works _GREAT_ for almost everything.  My problem is some bidirectional relationships that exist in my domain model (primarily as ManyToMany relationships).  

We solved the circular graph in JPA with Lazy Loading.  Unfortunately, GeDA insists on continuing around the graph in perpetuity until I hit a stack overflow.  Any clues on how I can prevent this sort of looping with my current model?

I have reviewed the existing examples and documentation and could not find a solution.  The @DtoParent seems to only work if a record has a single parent.  Can one side of a collection relationship be called the "Parent" to avoid circling to much (similar to JPA JoinColumns and MappedBy)?

Yes you can prevent this. However this requires some though as to what you are actually mapping.
There is a short article on the documentation site explaining how to deal with this: http://www.inspire-software.com/confluence/display/GeDA/Complex+class+hierarchies

You should not need to use DtoParent for this. 

Frank

unread,
May 21, 2013, 12:32:56 PM5/21/13
to geda-generic-dto-asse...@googlegroups.com
On Tuesday, May 21, 2013 9:03:21 AM UTC-4, Denis Pavlov wrote:
From email:

Yes you can prevent this. However this requires some though as to what you are actually mapping.
There is a short article on the documentation site explaining how to deal with this: http://www.inspire-software.com/confluence/display/GeDA/Complex+class+hierarchies

You should not need to use DtoParent for this. 
 
After trying to re-implement that link I found why it would not work for me.  We are using Vaadin's JPA container (along with the rest of the Vaadin tool kit) because it can utilize the JPA validation annotations that we already have on our JPA classes.  So in building our DTO framework (which contains many, but not all of the same fields as well as some additional information) we were also continuing the use of the JPA annotations to enable the dynamic generation of forms and validation.  Implementing the model according to that link breaks the JPA links and thus our automated form generation...

So - I suppose I will need to see what else we can do to work around this.


Denis Pavlov

unread,
May 21, 2013, 8:09:48 PM5/21/13
to geda-generic-dto-asse...@googlegroups.com
It is kind of hard to advise where to look next just from your description. Maybe you could get me a small sample to review?

But if you really get stuck there is a way to limit the hierarchy crawling. The assembler is completely stateless and hence there is no possibility to detect recursion of relationships. So if there is no way to break the relationship at logical level (i.e. have dto's with basic and full data) you can break the relationship in terms of mapping.

So, I presume you have some sort of collection with items that refer to parents (this is the most common case). You still will need a basic dto class as in the article which will be used as a filter. Then you can drop the DtoCollection mapping in favour of a DtoVirtualField with a converter. Inside that converter you can invoke assembler manually but instead of using full dto mapping DTOAssembler.newAssembler(DTO.class, Entity.class) you need to call DTOAssembler.newAssembler(BasicDTO.class, Entity.class).assembleDto(DTO, Entity). This way you still get the full DTO class but partially loaded data in it (because the assembler is using mapping up to BasicDTO.class). 

If you want me to give you more in depth recommendation I would really need a code sample. You can send it directly to my email as a zip.

Frank

unread,
May 22, 2013, 8:48:39 AM5/22/13
to geda-generic-dto-asse...@googlegroups.com
Actually, I did find a way to make this work.  It might not be super graceful, but it does work.

So - what I did was to split the DTO classes up as suggested in the link you sent.  I kept all of the basic fields and accessors/mutators in the base class but I also kept a JPA-annotated private field without any accessors / modifiers or GeDA annotation (it is essentially invisible to all but reflection).  In the extended object I put the same private field, but annotated it with DtoCollection and marked as @Transient.  The accessors/modifiers in the extended class take care of updating the base object as well as the local object.

While I know this can create a little extra memory (two collections stored instead of one), these objects will never have large collections.  The places where there are large collections are all paged and the page size is managed with a maximum size at the service layer.

Essentially:
@Entity
@Dto
Class UserBaseDto {
  @DtoField
  private Long id;
  @DtoField
  private String name;
  @ManyToMany
  @JoinTable(...)
  private Set<RoleBaseDto> roles;

  get/setId()...
  get/setName()...
}

@Entity
@Dto
class UserDto extends UserBaseDto {
  @DtoCollection(value = "roles", dtoBeanKey = "dtoRoleBase",
      entityBeanKeys = "entityRole", dtoToEntityMatcher = RoleMatcher.class,
      entityGenericType = Role.class, dtoCollectionClass = java.util.HashSet.class,
      entityCollectionClass = java.util.HashSet.class)
  @Transient
  private Set<RoleBaseDto> roles;

  get/setRoles{
    update super.roles / get super.roles, assign to local and return
  }
}

@Entity
@Dto
Class RoleBaseDto {
  @DtoField
  private Long id;
  @DtoField
  private String name;
  @ManyToMany(mappedBy="roles")
  private Set<UserBaseDto> users;

  get/setId()...
  get/setName()...
}

@Entity
@Dto
class RoleDto extends RoleBaseDto {
  @DtoCollection(value = "users", dtoBeanKey = "dtoUserBase",
      entityBeanKeys = "entityUser", dtoToEntityMatcher = UserMatcher.class,
      entityGenericType = User.class, dtoCollectionClass = java.util.HashSet.class,
      entityCollectionClass = java.util.HashSet.class)
  @Transient
  private Set<UserBaseDto> users;

  get/setUsers{
    update local and super.users / get super.users, assign to local and return
  }
}

This seems to work because 1) the DtoCollection is only marked on the extended class so is only evaluated there and 2) the JPA layer ignores the field in the extension class because of the @Transient annotation.


Denis Pavlov

unread,
May 22, 2013, 9:05:58 AM5/22/13
to geda-generic-dto-asse...@googlegroups.com
Excellent stuff :)
Reply all
Reply to author
Forward
0 new messages