Objectify Java Reference

100 views Asked by At
@Entity
public class Category {

    @Id
    private Long id;
    private String name;
    private String description;

    @Load
    private List<Ref<Subcategory>> subcategories = new ArrayList<Ref<Subcategory>>();
    @Load
    private Ref<Image> image;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public List<Subcategory> getSubcategories() {
        List<Subcategory> scs = new ArrayList<Subcategory>();
        for (Ref<Subcategory> sc : this.subcategories) {
            scs.add(sc.get());
        }
        return scs;
    }

    public void setSubcategory(Subcategory subcategory) {
        this.subcategories.add(Ref.create(subcategory));
    }

    public Image getImage() {
        if(image != null) {
            return image.get();
        }
        return null;
    }

    public void setImage(Image image) {
        this.image = Ref.create(image);
    }
}

@Entity
public class Subcategory {

    @Id
    private Long id;
    private String name;
    private String description;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

public class CategoryDTO {

    private Long id;

    @NotNull
    private String name;
    private String description;

    private List<Subcategory> subcategories = new ArrayList<Subcategory>();
    private Long imageId;

    public CategoryDTO() {
    }

    public CategoryDTO(Category category) {
        this.id = category.getId();
        this.name = category.getName();
        this.description = category.getDescription();
        this.subcategories = category.getSubcategories();

        if (category.getImage() != null) {
            this.imageId = category.getImage().getId();
        }
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public List<Subcategory> getSubcategories() {
        return subcategories;
    }

    public void setSubcategories(List<Subcategory> subcategories) {
        this.subcategories = subcategories;
    }

    public Long getImageId() {
        return imageId;
    }

    public void setImageId(Long imageId) {
        this.imageId = imageId;
    }

}

CategoryDAO

public class CategoryDAO {

    private static final Logger log = Logger.getLogger(CategoryService.class.getName());

    public static QueryResultIterator<Category> getCategories() {
        QueryResultIterator<Category> categories = ofy().load().type(Category.class).iterator();
        return categories;
    }
}

public class SubcategoryDAO {

    public static Subcategory createSubcategory(Long categoryId, Subcategory data) {
        // save sub category
        Subcategory subcategory = new Subcategory();
        if (data.getName() != null) {
            subcategory.setName(data.getName());
        }
        if (data.getDescription() != null) {
            subcategory.setDescription(data.getDescription());
        }
        ofy().save().entity(subcategory).now();

        Category category =
            ofy().load().type(Category.class).id(categoryId).get();
        category.setSubcategory(subcategory);
        ofy().save().entity(category).now();

        return subcategory;
    }
}

CategoryService

@Path("/categories")
public class CategoryService {

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public String getCategories() {
        try {
            List<CategoryDTO> categories = new ArrayList<CategoryDTO>();
            QueryResultIterator<Category> cats = CategoryDAO.getCategories();
            while (cats.hasNext()) {
                categories.add(new CategoryDTO(cats.next()));
            }
            Map<String, List<CategoryDTO>> map = new HashMap<String, List<CategoryDTO>>();
            map.put("categories", categories);
            return Helper.prepareResponse(map);
        } catch (Exception e) {
            LogService.getLogger().severe(e.getMessage());
            throw new WebApplicationException(500);
        }
    }
}

Problem:-

When i hit getCategories service, it is showing unexpected behaviour.Instead of showing all the subcategories, it is showing random no of different subcategories every time.

For example say, first i save a category "c" then i save subcategories "sa", "sb" and "sc"

On hitting getCategry service,

Expected Behaviour -

{
    "status": 200,
    "categories" : [{ 
        "name":a, 
        "subcategories": [
            {
                "name":"sa"
            }, 
            {
                "name":"sb"
            }, 
            {
                "name":"sc"
            } 
        ]
    }]
}

Outputs i get is something like this -

{
    "status": 200,
    "categories" : [{ 
        "name":a, 
        "subcategories": [
            {
                "name":"sa"
            }, 
            {
                "name":"sc"
            } 
        ]
    }]
}

or

{
    "status": 200,
    "categories" : [{ 
        "name":a, 
        "subcategories": [{
            "name":"sb"
        }]
    }]
}
2

There are 2 answers

3
Gabriel On

Welcome to the wonderful world of eventual consistency. When I encountered something like this, using ObjectifyService.begin() instead of ObjectifyService.ofy() resolved it. Unlike ofy(), begin() gets you fresh data every time.

0
stickfigure On

To summarize this question, you're performing a query (give list of all categories) and getting back inconsistent results.

This is the system working as advertised. Read this: https://cloud.google.com/appengine/docs/java/datastore/structuring_for_strong_consistency

Eventual consistency is something that you learn to live with and work around when you need it. There is no way to force a query to be strongly consistent without changing the structure of your data - say, put it under a single entity group - but that has repercussions as well. There is no free lunch if you want a globally replicated, infinitely-scalable database.

In addition to eventual consistency, the datastore has no defined ordering behavior if you do not specify a sort order in your query. So that might add to your confusion.