google-app-engine - 使用 JPA 的 Google 应用引擎中的双向一对多关系

标签 google-app-engine jpa google-cloud-datastore jackson datanucleus

我想在 GAE 数据存储中创建实体组,以便一个城市包含多个郊区。以下是我的代码:-

//city.java

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
@Entity
public class City
{
    @Id
    private String name;

    @OneToMany(mappedBy="city", cascade=CascadeType.ALL)
    private Suburban[] suburbans;

    public String getName()
    {
        return name;
    }

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

    public Suburban[] getSuburbans()
    {
        return suburbans;
    }

    public void setSuburbans(Suburban[] suburbans)
    {
        this.suburbans = suburbans;
    }

}

//suburban.java

@JsonIdentityInfo(generator=ObjectIdGenerators.IntSequenceGenerator.class, property="@id")
@Entity
public class Suburban
{

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Key id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)
    private City city;

    public City getCity()
    {
        return city;
    }

    public void setCity(City city)
    {
        this.city = city;
    }

    public String getName()
    {
        return name;
    }

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

    public Key getId()
    {
        return id;
    }
}

我使用 google-plugin 的 eclipse 选项“生成云端点类”自动生成 cityendpoint 类。

//CityEndpoint.java

@Api(name = "cityendpoint", namespace = @ApiNamespace(ownerDomain = "zestbuds.com", ownerName = "zestbuds.com", packagePath = "android"))
public class CityEndpoint
{

    /**
     * This method lists all the entities inserted in datastore.
     * It uses HTTP GET method and paging support.
     *
     * @return A CollectionResponse class containing the list of all entities
     * persisted and a cursor to the next page.
     */
    @SuppressWarnings({ "unchecked", "unused" })
    @ApiMethod(name = "listCity")
    public CollectionResponse<City> listCity(@Nullable @Named("cursor") String cursorString, @Nullable @Named("limit") Integer limit)
    {

        EntityManager mgr = null;
        Cursor cursor = null;
        List<City> execute = null;

        try
        {
            mgr = getEntityManager();
            Query query = mgr.createQuery("select from City as City");
            if (cursorString != null && cursorString != "")
            {
                cursor = Cursor.fromWebSafeString(cursorString);
                query.setHint(JPACursorHelper.CURSOR_HINT, cursor);
            }

            if (limit != null)
            {
                query.setFirstResult(0);
                query.setMaxResults(limit);
            }

            execute = (List<City>) query.getResultList();
            cursor = JPACursorHelper.getCursor(execute);
            if (cursor != null)
                cursorString = cursor.toWebSafeString();

            // Tight loop for fetching all entities from datastore and accomodate
            // for lazy fetch.
            for (City obj : execute)
                ;
        } finally
        {
            mgr.close();
        }

        return CollectionResponse.<City> builder().setItems(execute).setNextPageToken(cursorString).build();
    }

    /**
     * This method gets the entity having primary key id. It uses HTTP GET method.
     *
     * @param id the primary key of the java bean.
     * @return The entity with primary key id.
     */
    @ApiMethod(name = "getCity")
    public City getCity(@Named("id") String id)
    {
        EntityManager mgr = getEntityManager();
        City city = null;
        try
        {
            city = mgr.find(City.class, id);
        } finally
        {
            mgr.close();
        }
        return city;
    }

    /**
     * This inserts a new entity into App Engine datastore. If the entity already
     * exists in the datastore, an exception is thrown.
     * It uses HTTP POST method.
     *
     * @param city the entity to be inserted.
     * @return The inserted entity.
     */
    @ApiMethod(name = "insertCity")
    public City insertCity(City city)
    {
        EntityManager mgr = getEntityManager();
        try
        {
            if (containsCity(city))
            {
                throw new EntityExistsException("Object already exists");
            }
            mgr.persist(city);
        } finally
        {
            mgr.close();
        }
        return city;
    }

    /**
     * This method is used for updating an existing entity. If the entity does not
     * exist in the datastore, an exception is thrown.
     * It uses HTTP PUT method.
     *
     * @param city the entity to be updated.
     * @return The updated entity.
     */
    @ApiMethod(name = "updateCity")
    public City updateCity(City city)
    {
        EntityManager mgr = getEntityManager();
        try
        {
            if (!containsCity(city))
            {
                throw new EntityNotFoundException("Object does not exist");
            }
            mgr.persist(city);
        } finally
        {
            mgr.close();
        }
        return city;
    }

    /**
     * This method removes the entity with primary key id.
     * It uses HTTP DELETE method.
     *
     * @param id the primary key of the entity to be deleted.
     */
    @ApiMethod(name = "removeCity")
    public void removeCity(@Named("id") String id)
    {
        EntityManager mgr = getEntityManager();
        try
        {
            City city = mgr.find(City.class, id);
            mgr.remove(city);
        } finally
        {
            mgr.close();
        }
    }

    private boolean containsCity(City city)
    {
        EntityManager mgr = getEntityManager();
        boolean contains = true;
        try
        {
            City item = mgr.find(City.class, city.getName());
            if (item == null)
            {
                contains = false;
            }
        } finally
        {
            mgr.close();
        }
        return contains;
    }

    private static EntityManager getEntityManager()
    {
        return EMF.get().createEntityManager();
    }

}

最初,我没有使用@JsonIdentityInfo,因此我得到了java.io.IOException:com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException:无限递归(StackOverflowError).看完thread ,我认识到我的错误是 jackson 造成的。

阅读后Thread ,我决定使用@JsonIdentityInfo。现在我得到了 java.io.IOException:com.google.appengine.repackaged.org.codehaus.jackson.map.JsonMappingException:您刚刚尝试访问字段“suburbans”,但在分离对象时该字段并未分离。要么不访问此字段,要么在分离对象时分离它。 (通过引用链:com.google.api.server.spi.response.CollectionResponse["items"]->com.google.appengine.datanucleus.query.StreamingQueryResult[0]->com.zestbuds.android.City["郊区"])

即使我使用 Cascade.ALL,为什么我的郊区并不分离?

最佳答案

问题终于解决了。

不需要使用 @JsonIdentityInfo 。我只需要删除具有 @ManyToOne 注释的类成员的 getter 和 setter 方法(在我的例子中,我删除了 getCity() 和 setCity())。

Here是datanucleus提供的用于双向一对多映射的示例。

关于google-app-engine - 使用 JPA 的 Google 应用引擎中的双向一对多关系,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18694049/

相关文章:

java - 如何在没有外键的情况下匹配 hibernate/JPA 表

java.lang.Long 无法转换为 java.lang.Integer JPA GAE 和枚举

java - 应用引擎 : How to persist related entities using Objectify

java - 无法通过长 ID 删除实体

java - JDO 查询中的多个类

python - AppEngine - 写入限制为每秒 1 次

java - 如何使用 "size"作为 hibernate/jpa 实体中的字段名称?

google-app-engine - 在 Google App Engine 上使用 Google Apps 昵称发送电子邮件

java - 如何在Google App Engine灵活环境中配置oracle jdk

google-app-engine - GAE-NDB : Performance of JsonProperty vs StructuredProperty?