A.Onur Özcan
A.Onur Özcan

Reputation: 143

Infinite Loop When Collection Mapping With Dozer

I'm developing a project which uses BackboneJS in front-end and Java - Spring Core in back-end. I have a problem about mapping entity(domain) objects to DTO objects. I am getting an error message like that :

org.apache.cxf.interceptor.Fault: Infinite recursion (StackOverflowError) (through reference chain: com.countdown.dto.CategoryDTO["countdownList"]->java.util.ArrayList[0]->com.countdown.dto.CountdownDTO["category"]->.......

User.java

@Entity
@Table(name = "Users")
public class User implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "USER_ID", nullable = false)
    private int id;

    @Column(name = "EMAIL", nullable = false, unique = true)
    private String email;

    @Column(name = "NAME_SURNAME", nullable = false)
    private String nameSurname;

    @Column(name = "PASSWORD", nullable = false)
    private String password;

    @Column(name = "USERNAME", nullable = false, unique = true)
    private String username;

    @Column(name = "REGISTER_DATE", nullable = false)
    private Date registerDate;

    @ManyToOne
    @JoinColumn(name = "ROLE_ID")
    private Role role;

    @OneToMany(mappedBy = "createUser")
    private List<Countdown> createCountdownList = new ArrayList<Countdown>();

    @OneToMany(mappedBy = "updateUser")
    private List<Countdown> updateCountdownList = new ArrayList<Countdown>();

    @ManyToMany
    @JoinTable(name = "FOLLOWINGS",
            joinColumns = @JoinColumn(name = "USER_ID"),
            inverseJoinColumns = @JoinColumn(name = "COUNTDOWN_ID"))
    private List<Countdown> followings = new ArrayList<Countdown>();

    //Getters and setters..

}

Role.java

@Entity
public class Role implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "ROLE_ID")
    private int id;

    @Column(name = "ROLE_NAME", nullable = false)
    private String roleName;

    @OneToMany(mappedBy = "role",fetch = FetchType.LAZY)
    List<User> userList = new ArrayList<User>();
 }

Countdown.java

@Entity
@Table(name = "COUNTDOWN")
public class Countdown implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "COUNTDOWN_ID")
    private int id;

    @Column(name = "COUNTDOWN_NAME", nullable = false)
    private String countdownName;

    @Column(name = "COUNTDOWN_DATE", nullable = false)
    private Date countdownDate;

    @Column(columnDefinition = "varchar(5000)")
    private String countdownDescription;

    @JoinColumn(name = "CATEGORY_ID", nullable = false)
    @ManyToOne
    private Category category;

    @JoinColumn(name = "CREATE_USER", nullable = false)
    @ManyToOne
    private User createUser;

    @Column(name = "CREATE_DATE", nullable = false)
    private Date createDate;

    @JoinColumn(name = "UPDATE_USER", nullable = false)
    @ManyToOne
    private User updateUser;

    @Column(name = "UPDATE_DATE", nullable = false)
    private Date updateDate;

    @Column(name = "CREATE_USER_IP", nullable = false)
    private int createIP;

    @ManyToMany
    private List<User> followers = new ArrayList<User>();

}

Category.java

@Entity
@Table(name="CATEGORY")
public class Category implements Serializable{

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name="CATEGORY_ID")
    private int id;

    @Column(name = "CATEGORY_NAME" , nullable = false)
    private String categoryName;

    @OneToMany(mappedBy = "category")
    private List<Countdown> countdownList = new ArrayList<Countdown>();

}

Business Logic : CategoryServiceImpl.java I'm getting error in forEach loop.

@Transactional(readOnly = true)
public List<CategoryDTO> getAllCategories() {
    List<Category> categoryList;
    List<CategoryDTO> categoryDTOList = new ArrayList<CategoryDTO>();

    logger.debug("getAllCategories called");

    try {
        categoryList = categoryDAO.findAll();
        for(Category category : categoryList){
            categoryDTOList.add(mapper.map(category,CategoryDTO.class));
        }
    }catch (NoResultException e){
        logger.error("getAllCategories method : No Category wasn't found");
        logger.warn(e,e);
    }catch (Exception e){
        logger.error("getAllCategories method : Categories wasn't found");
        logger.warn(e,e);
    }
    return categoryDTOList;
}

Also Do I have to use DTO object in Presentation Layer? Can I use entity objects in presentation layer instead of DTO objects?

How can I solve this problem? Sorry my bad english. Thank you!

Upvotes: 1

Views: 1878

Answers (2)

John the Traveler
John the Traveler

Reputation: 514

For those who are struggling with infinite recursion issue in Dozer.

I use mapId to define a leaf object and stops the recursion.

Let assume we have two entities Course and Teacher, which contains a Many-to-Many relationship, and we want to convert the following object graph to one represented by CourseDTO and TeacherDto. And we hope Dozer stops at the 3rd level.

Teacher 1 ---> m Course 1 ---> m Teacher ---> ...
1st level        2nd level       3rd level

We can first define the following definition for Teacher to TeacherDTO conversion.

This first mapping is used for the root Teacher entity. Include any other fields you need in the mapping.

mapping(Teacher.class, TeacherDTO.class,
                    TypeMappingOptions.oneWay()
                    , mapNull(false)
            ).fields("courses", "courses");

The following mapping will prevent Dozer from going further to map the contained Course. We define a mapId teacherLeaf for it. Exclude the fields that cause the infinite recursion. (In my example, it's courses) Include any other fields you need in the mapping.

                mapping(Teacher.class, TeacherDTO.class,
                    TypeMappingOptions.oneWay(), TypeMappingOptions.mapId("teacherLeaf")
                    , mapNull(false)
            ).exclude("courses");

The last one is the mapping rule for Course to courseDTO. The key is that we tell the mapper to use the teacherLeaf mapping rule defined previously to convert the contained Teachers.

                mapping(Course.class, CourseDTO.class,
                    TypeMappingOptions.oneWay()
                    , mapNull(false)
            ).fields("teachers", "teachers", useMapId("teacherLeaf"));

Hope this helps!

I use Dozer 6.1.0.

Upvotes: 1

ooozguuur
ooozguuur

Reputation: 3476

Please Try :

    @Transactional(readOnly = true)
    public List<CategoryDTO> getAllCategories() {
        List<Category> categoryList;
        List<CategoryDTO> categoryDTOList = new ArrayList<CategoryDTO>();

        logger.debug("getAllCategories called");

        try {
            categoryList = categoryDAO.findAll();

            for(Category category : categoryList){
                  if(category.getCountdownList() != null && !category.getCountdownList().isEmpty()){
                      for(Countdown countdown : category.getCountdownList()){
                          countdown.setCategory(null);
                      }
                   }
                categoryDTOList.add(mapper.map(category,CategoryDTO.class));
            }
        }catch (NoResultException e){
            logger.error("getAllCategories method : Hata: No Category wasn't found");
            logger.warn(e,e);
        }catch (Exception e){
            logger.error("getAllCategories method : Hata: Categories wasn't found");
            logger.warn(e,e);
        }
        return categoryDTOList;
    }

Upvotes: 1

Related Questions