Objectify - how to @Load a List<Ref<?>>?
Asked Answered
K

3

6

I have an entity, with a field containing a list to Refs to other entities (always 4). I'm trying to get some entities, and dispatch them for a jsp for display. I want all the Refs in the field to be loaded as well, and to access them in the jsp.

Here is my basic structure:

@Entity
public class Question {
    @Id Long id;
    @Index String question;
    @Load List<Ref<Answer>> answers = new ArrayList<Ref<Answer>>();
}

When I'm fetching question like this, obviously there's an error in the jsp. Makes sense, because answers field is not a list of answers, but of refs:

ObjectifyService.register(Question.class);
ObjectifyService.register(Answer.class);

List<Question> questions = ofy().load().type(Question.class).limit(50).list();

req.setAttribute("questions", questions);
try { 
    getServletContext().getRequestDispatcher("/admin/view-questions.jsp").forward(req, resp); 
} catch (ServletException e) {
    System.out.println (e.getMessage());
}

So how do access the answers in the jsp? is the only way to manually loop through the questions and do a get() for the answers field?

Kailakaile answered 11/11, 2012 at 19:35 Comment(0)
Q
8

You might find this handy:

public class Deref {
    public static class Func<T> implements Function<Ref<T>, T> {
        public static Func<Object> INSTANCE = new Func<Object>();

        @Override
        public T apply(Ref<T> ref) {
            return deref(ref);
        }
    }

    public static <T> T deref(Ref<T> ref) {
        return ref == null ? null : ref.get();
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static <T> List<T> deref(List<Ref<T>> reflist) {
        return Lists.transform(reflist, (Func)Func.INSTANCE);
    }
}

Using this, you can provide a method like:

@Entity
public class Question {
    @Id Long id;
    @Index String question;
    @Load List<Ref<Answer>> answers = new ArrayList<Ref<Answer>>();
    public List<Answer> getAnswers() { return Deref.deref(answers); }
}

One caveat is that it's a readonly list. With a little creativity you can create a writeable list, but it requires creating a ReversibleFunction and a new List.transform() method. If you just want something simple for JSPs, you might not want to worry about it.

Alternatively, you can use the 'value' property of a Ref in most expression languages.

Quadrilateral answered 12/11, 2012 at 17:20 Comment(1)
@Quadrilateral by adding an Answer (myQuestion.getAnswers().add(newAnswer);) I get an exception: java.lang.UnsupportedOperationException at java.util.AbstractList.add(Unknown Source)Ulster
C
8

How about something like this:

@Entity
public class Question {
    @Id Long id;
    @Index String question;
    @Load List<Ref<Answer>> answers = new ArrayList<Ref<Answer>>();

    public List<Answer> loadAnswers()
    {
        return new ArrayList<Answer>(ofy().load().refs(answers).values());
    }
}

Since you've already loaded the entities using the @Load annotation, I think objectify will just pull them from the session cache (they won't be loaded 2x).

And if you weren't using the @Load annotation then this method would perform a batch load which is probably better performance than looping through the Refs and calling get() individually.

Cosme answered 9/2, 2015 at 15:57 Comment(5)
This is the best answer as of 2015.Schaffel
The reason this is a bad idea is the function loadAnswers() will only work in the backend code, although the client libraries generated using appEngine will also contain the loadAnswers() function and it wont work at all in the clients.Imbecilic
I think it just depends on your needs and the framework you are using, not everyone will be using app engine to generate clients; the original poster only mentioned JSP files. The basic concept should still be sound you may just need to refactor the code slightly to fit your own needs.Cosme
Thanks for this . Accepted solution was not working .Gaston
This is the best answer as of 2019 ;)Prejudice
T
-1

What happens if you change the List type?

@Entity
public class Question {
    @Id Long id;
    @Index String question;
    @Load List<Answer> answers = new ArrayList<Answer>();
}

I check it and all goes well, but in the documentation (https://code.google.com/p/objectify-appengine/wiki/Entities#Ref_s) there are not any reference to this case.

Is it safe to do it?

Tungstate answered 8/5, 2013 at 17:4 Comment(2)
You are storing an object as opposed to a reference this way. Do not do this.Schaffel
It will create an Embedded Entity.Imbecilic

© 2022 - 2024 — McMap. All rights reserved.