Frage

So I am having an issue that someone here might be able to help me out with Dozer.

Background: I have Dozer set up to map my persistence entities to their DTO classes. It is pretty simple, I am just creating an exact replica of my entity class as a POJO and allow dozers wild card to see that the name of the field matches the sources field. I am handling hibernates lazy loading issue with a custom mapper as done here. I am telling Dozer how to map each class via a class that scans for an annotation called @EntityMapping(DTOxxx.class) within the entity. Then it adds it to the mapper addMapping(builder)

The issue: (Read the research at the end, for up to date information, but it will help to gain context by reading all of this too) The issue is that Dozer isn't mapping my collections properly in SOME instances. For example, in my CategoryEntity class I have a collection of other entities that Dozer needs to map. What happens is that dozer finds the collection which has in this case 2 items and only maps 1 item in the new DTO class collection.

enter image description here

As you can see in the image after toDomain is called (this has the mapper.map(source, desination) dozer call in it) the DTO only has 1 of the 2 objects that it should have mapped into it from the entity. Here is the toDomain method encase you want to see it:

@Transactional(readOnly=true)
public <T extends DomainObject> T toDomain(Class<T> clazz, Entity entity) {
    if (entity == null) {
        return null;
    }

    T domain = getCachedDomainObjects(clazz, entity.getId());
    if (domain == null) {
        domain = dozerMapper.map(entity, clazz);
        cacheDomainObject(domain);
    }
    return domain;
}

I have made sure its not grabbing a cached entity if you are thinking that.

So I am a bit stumped as to why this is happening in some cases, and not in other cases. I can't see any obvious differences in the occasions it does work and the occasions it doesn't work. If anyone has ran into an issue like this before and thinks they will be able to help me out, that would be fantastic! Here are my classes from the example of the issue:

CategoryEntity.java:

@EntityMapping(Category.class)
@javax.persistence.Entity(name = "categories")
public class CategoryEntity implements Entity, PureTable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(unique = true, nullable = false)
    private int id = Entity.UNSAVED_ID;

    @OneToMany(mappedBy = "pk.category", fetch = FetchType.LAZY)
    @Cascade({CascadeType.SAVE_UPDATE})
    private Set<IncidentJoinCategoryEntity> incidentJoinCategories =
        new HashSet<IncidentJoinCategoryEntity>();

    @Override
    public int getId() {
        return this.id;
    }
    public void setId(int id) {
        this.id = id;
    }

    public Set<IncidentJoinCategoryEntity> getIncidentJoinCategories() {
        return incidentJoinCategories;
    }
    public void setIncidentJoinCategories(Set<IncidentJoinCategoryEntity> 
        incidentJoinCategories) {
        this.incidentJoinCategories = incidentJoinCategories;
    }
}

This class has a DTO class that matches its values entirely:

Category.java:

public class Category {

    int id;

    Set<IncidentJoinCategory> incidentJoinCategories= 
    new HashSet<IncidentJoinCategory>();

    @Override
    public int getId() {
        return id;
    }
    @Override
    public void setId(int id) {
        this.id = id;
    }

    public Set<IncidentJoinCategory> getIncidentJoinCategories() {
        return incidentJoinCategories;
    }
    public void setIncidentJoinCategories(Set<IncidentJoinCategory> 
        incidentJoinCategories) {
        this.incidentJoinCategories = incidentJoinCategories;
    }
}

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! RESEARCH !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

EDIT #1:

Okay! So I have spent hours debugging this issue to find out what is going on here. Turns out the issue is in MappingProcessor class line 749 (Dozer 5.4.0) or line 766 for latest sources (but I have not checked if this is still an issue in the latest sources, doubt that it is fixed though).

This line is

((Set) field).addAll(result);

What it is trying to map here is a

HashSet<IncidentJoinCategoryEntity>

The addAll(result) is only adding 1 item to the ((Set) field) collection. Result which has 2 items in it (during debug it is also size 2 I will provide my snapshot of the variables) is only adding 1 value to the ((Set)field) cast.

result  LinkedHashSet<E>  (id=220)  
    map LinkedHashMap<K,V>  (id=248)    
        accessOrder false   
        entrySet    HashMap$EntrySet  (id=251)  
        hashSeed    -1187793029 
        header  LinkedHashMap$Entry<K,V>  (id=253)  
        keySet  HashMap$KeySet  (id=5829)   
        loadFactor  0.75    
        modCount    2   
        size    2   
        table   HashMap$Entry<K,V>[16]  (id=258)    
        threshold   12  
        useAltHashing   false   
        values  null    
field   HashSet<E>  (id=221)    
    map HashMap<K,V>  (id=247)  
        entrySet    HashMap$EntrySet  (id=5856) 
        hashSeed    1372273954  
        keySet  HashMap$KeySet  (id=5821)   
        loadFactor  0.75    
        modCount    2   
        size    1   
        table   HashMap$Entry<K,V>[16]  (id=5822)   
        threshold   12  
        useAltHashing   false   
        values  null    

enter image description here

EDIT #2:

Downloaded the source for more debugging:

if (field == null) {
  Class<? extends Set<?>> destSetType = (Class<? extends Set<?>>) fieldMap.getDestFieldType(destObj.getClass());
  return CollectionUtils.createNewSet(destSetType, result);
} else {
  System.out.println("----IN----");
  // Bug #1822421 - Clear first so we don't end up with the removed orphans again
  Set ret = (Set) field;
  ret.clear();
  //((Set) field).addAll(result);
  for(Object res : result) {
      System.out.println("FOUND " + res.toString());
      ret.add(res);
  }
  System.out.println("END SIZE " + ret.size());
  System.out.println("----OUT----");
  return ret;
}

Output for this case:

----IN----
FOUND nz.co.doltech.ims.project.shared.domains.joins.IncidentJoinCategory@3e2
FOUND nz.co.doltech.ims.project.shared.domains.joins.IncidentJoinCategory@3e2
END SIZE 1
----OUT----

Its output is 2 items, but as you can see @3e2 they are the same item for some reason. So when you call addAll it removes the duplicate and leaves us with just 1 item. Why is Dozer accidentally mapping 2 of the same values? I checked to make sure the source objects collection didn't have the same items doubled up, and sure enough it isn't. Strange indeed.

EDIT #3:

I have done further tests with little luck here. It is indeed an issue with Dozer mapping 2 of the same values and addAll knocks the duplicate off, making it just one item in the list. Unfortunately I can't debug the recursive methods in addToSet very easily to identify why this is happening.

Will update if I figure anything else out, otherwise I am out of ideas on this one haha.

War es hilfreich?

Lösung

Turns out this is not actually a Dozer bug. The debugging suggested that Dozer was the culprit, but I don't think that is the case. Reason I think this is because I changed over to another mapper with the same issue, so unless this new mapper has the same issue (lol) its not Dozer. If anyone has any idea as to why this is happening for me, I would appreciate the help with that.

My guess at the moment is the hibernate custom field mapper that I have in place to deal with lazy load sensitive collections. The only reason I disregarded this in the first place is because when I started debugging Dozer, it seemed that Dozer was mapping the fields before it returned from addToSet, so I wrongly assumed it had already applied the custom field mapping.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top