avoid stackoverflow while reading and popular relationships

Asked

Viewed 114 times

2

I’m making a library that populates any model with random values, to be used in tests, but it turns out that when I have a relation like below, I get a StackOverflowException

Author

@Entity
public class Author implements Serializable {

private static final long serialVersionUID = 1L;

@Id
private Long id;

private String name;

@OneToMany
private List<Book> books = new ArrayList<Book>();

and Book

@Entity
public class Book implements Serializable {


@ManyToOne
private Author author;

the code that reads the fields is like this

public <T> T fakeIt(Class<T> clazz) throws FakerException {
if(clazz.getAnnotation(Entity.class) == null){
    throw new FakerException("The class "+ clazz.getName()+ "is not an entity");
}
try {
    T faked  = clazz.newInstance();
    for(Field f : clazz.getDeclaredFields()){
    if(f.getName().equals("serialVersionUID"))
        continue;

    System.out.println("Genearting value for "+f.getName() + " on " + f.getDeclaringClass());

    f.setAccessible(true);
    f.set(faked, getValueForField(f));
    }
    return faked;
} catch(Exception e){
    throw new FakerException(e);
}
}

private Object getValueForField(Field f) throws Exception {
if(f.isAnnotationPresent(UseGenerator.class)){
    Generator<?> gen = (Generator<?>) f.getAnnotation(UseGenerator.class).generator().newInstance();
    return gen.genearte();

} else if(f.isAnnotationPresent(ManyToOne.class)){
    return fakeIt(f.getType());

} else if(f.isAnnotationPresent(OneToMany.class)){
    Class<?> toFake = extractTypeFromList(f);

    List<Object> fakedObjects = new ArrayList<Object>();
    for(int i = 0; i < 6; i++){
    fakedObjects.add(fakeIt(toFake));
    }

    return fakedObjects;
}
// Other types
String clazzType = f.getType().getSimpleName();
Generator<?> generator = defaultGenerators.get(clazzType.toLowerCase());

if(generator != null)
    return generator.genearte();

return null;
}

private Class<?> extractTypeFromList(Field f) {
ParameterizedType parameterizedType = (ParameterizedType) f.getGenericType();
Class<?> type = (Class<?>) parameterizedType.getActualTypeArguments()[0];

return type;
}

in the case, fakeIt is the function that generates the random values. in this case, it falls into the field books and when he calls again fakeIt, will try to create a new Author that will fall in the field books and so on.
how best to avoid this?

  • Could you add the error to the question? At first I don’t see where one might occur StackOverflow. That one fakeIt ends up calling this same method?

  • the fakeIt calls the method getValueForField(Field F) who calls again the fakeIt in case of a relationship. I can post the whole code if it helps

  • Post yes, at least the getValueForField and fakeIt method.

  • @Dener edited!

  • Luiz, the StackOverflow is because of the bidirectional relationship: when will create a author it creates a list of book and these will also try to create a author, from then on it will loop. You will have to seek to treat bi-directional relations.

  • It makes a logic more or less like this: create checkIfFieldHasClass(field, clazz, faked) in this method obtain the class of field checking whether this class has the clazz as an attribute, if,set your instance created faked, if you don’t ask to generate this value.

  • cool, it worked. you could post as an answer to accept?

  • Ok! Sorry for the delay to answer! Solving some bugs here I ended up running out of time

Show 3 more comments

1 answer

4


Luiz

The cause of StackOverflow is the bidirectional relationship between classes Book and Author.

When you create a author and tries to give values to the attributes, a List<Book>, and each element of that list will also create Author, which in turn creates a new list of books and from there follows in loop.

To solve the problem, you will have to seek to treat the relationships bidirectional.

Just like you said in the comment, you can solve the problem by creating a method that fetches the value of field you check if the object itself fake is not the attribute of the field, something like:

private boolean checkIfFieldHasClass(Field f, Class<?> clazz){
   Class<?> fieldClass = f.getClass();

   for (Field declaredField : fieldClass.getDeclaredFields()) {
        if (declaredField.getClass().equals(clazz)) {
            return true;
        }
    }

    return false;
}

If the field have an attribute of the type clazz, then set the instance faked in this attribute, otherwise ask to generate this value.

In the commentary I passed the faked as parameter also by misconception. It is best to leave this above method returning a boolean so that it becomes more readable and can use for other times.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.