Google App Engine中的数据存储(4)-关系

时间:2021-10-28 16:39:38

以下内容来至于Google App Engine网站的摘抄

有主的一对一关系

单向一对一的关系

ContactInfo.java

import com.google.appengine.api.datastore.Key;
// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class ContactInfo {
   
@PrimaryKey
   
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   
private Key key;

   
@Persistent
   
private String streetAddress;

   
// ...
}

Employee.java

import ContactInfo;
// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Employee {
   
@PrimaryKey
   
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   
private Long id;

   
@Persistent
   
private ContactInfo contactInfo;

   
ContactInfo getContactInfo() {
       
return contactInfo;
   
}
   
void setContactInfo(ContactInfo contactInfo) {
       
this.contactInfo = contactInfo;
   
}

   
// ...
}

持久对象表示为数据存储区中具有两种不同类型的两个不同实体。

使用实体组关系来表示该关系:子代的键使用父代的键作为其父实体组。

当实用程序使用父对象的字段访问子对象时,JDO 实现执行父实体组查询以获取子对象。

子类必须具有类型包含父键信息的键字段,这些父键信息为 Key 或以字符串编码的键值。

 

双向一对一关系

用子类字段上的批注来声明这些字段表示双向关系。

子类字段必须具有带参数 mappedBy = "..." 的 @Persistent批注,该参数的值是父类字段的名称。

如果填充一个对象上的字段,则会自动填充另一个对象上对应的引用字段。

 

ContactInfo.java

import Employee;

// ...
   
@Persistent(mappedBy = "contactInfo")
   
private Employee employee;
当初次访问子对象时,将从数据存储区中载入这些对象。如果没有访问父对象上的子对象,则绝不会载入该子对象的实体。
有主的一对多关系

单向一对多的关系

采用相关类的集合

Employee.java

import java.util.List;

// ...
   
@Persistent
   
private List<ContactInfo> contactInfoSets;
双向一对多的关系

父类字段使用批注 @Persistent(mappedBy = "..."),其值是子类字段的名称:

Employee.java

import java.util.List;

// ...
   
@Persistent(mappedBy = "employee")
   
private List<ContactInfo> contactInfoSets;

ContactInfo.java

import Employee;

// ...
   
@Persistent
   
private Employee employee;
定义数据类:集合中列出的集合类型支持一对多关系。但是,数组不支持一对多关系。
App Engine 不支持 join 查询:您无法使用子实体的属性来查询父实体。

无主的关系

可以使用 Key 值代替模型对象的实例(或实例集合)来管理这些关系。您可以将存储键对象看作在两个对象之间建模任意“外键”。无主的一对一关系假如我们要对人和食物进行建模,其中一个人只能有一种最喜欢的食物而一种最喜欢的食物并不属于该人,因为它可以是任何人最喜欢的食物:

Person.java

// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Person {
   
@PrimaryKey
   
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   
private Long id;

   
@Persistent
   
private Key favoriteFood;

   
// ...
}

Food.java

import Person;
// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Food {
 
  @PrimaryKey
   
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   
private Long id;

   
// ...
}
在此示例中,我们为 Person 提供类型为 Key 的成员(其中 Key 为 Food 对象的唯一标识符),而不是为 Person 提供类型为 Food 的成员来表示该人最喜欢的食物。请注意,除非 Person 的实例和 Person.favoriteFood 所引用的 Food 实例位于同一实体组中,否则不可能在单个事务中更新该人和该人最喜欢的食物。
无主的一对多关系

现在,假如我们要让一个人有多种最喜欢的食物。同样,一种最喜欢的食物并不属于该人,因为它可以是任何人最喜欢的食物:

Person.java

// ... imports ...

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Person {
 
  @PrimaryKey
   
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
   
private Long id;

   
@Persistent
   
private Set<Key> favoriteFoods;

   
// ...
}
在此示例中,我们为 Person 提供类型为 Set<Key> 的成员(其中 set 包含 Food 对象的唯一标识符),而不是为 Person 提供类型为 Set<Food> 的成员来表示该人最喜欢的食物。请注意,除非 Person 的实例和包含在 Person.favoriteFoods 中的 Food 实例位于同一个实体组中,否则不可能在单个事务中更新该人和该人最喜欢的食物。
无主的多对多关系

可以通过保留关系双方的键集合来建模多对多关系。让我们调整本示例,使得 Food 记录将其认为是自己最喜欢的食物的人:

Person.java

import java.util.Set;
import com.google.appengine.api.datastore.Key;

// ...
   
@Persistent
   
private Set<Key> favoriteFoods;

Food.java

import java.util.Set;
import com.google.appengine.api.datastore.Key;

// ...
   
@Persistent
   
private Set<Key> foodFans;

在此示例中,Person 保留唯一标识 Food 对象(最喜欢的食物)的一组 Key 值,而 Food 保留唯一标识 Person 对象(将其视为自己最喜欢的食物)的一组 Key值。

在使用 Key 值建模多对多关系时,要了解应用程序有必要保留关系双方:

Album.java


// ...
public void addFavoriteFood(Food food) {
    favoriteFoods
.add(food.getKey());
    food
.getFoodFans().add(getKey());
}

public void removeFavoriteFood(Food food) {
    favoriteFoods
.remove(food.getKey());
    food
.getFoodFans().remove(getKey());
}
注意,除非 Person 的实例和包含在 Person.favoriteFoods 中的 Food 实例位于同一个实体组中,否则不可能在单个事务中更新该人和该人最喜欢的食物。如果无法使对象位于同一个实体组中,则应用程序必须能够实现不必相应地更新将该食物视为自己最喜欢的食物的人集合即可更新某人最喜欢的食物,反之,还必须能够实现不必相应地更新该人最喜欢的食物即可更新将某食物视为自己最喜欢的食物的人集合。

关系、实体组和事务

当拥有有主关系的对象保存到数据存储区中时,会自动保存能够沿关系到达且需要保存的所有其他对象(新对象或自上次载入以来已更改的对象)。这对事务和实体组意义重大。

请思考使用上述 Employee 与 ContactInfo 类之间的单向关系的下例:

    Employee e = new Employee();
   
ContactInfo ci = new ContactInfo();
    e
.setContactInfo(ci);

    pm
.makePersistent(e);

当使用 pm.makePersistent() 方法保存新的 Employee 对象时,新的相关 ContactInfo 对象也会自动保存。由于这两个对象都是新对象,因此 App Engine 将在同一个实体组中新建两个实体,将 Employee 实体用作 ContactInfo 实体的父实体。同样,如果已经保存 Employee 对象而相关的 ContactInfo 对象是新对象,则 App Engine 将创建 ContactInfo 实体,将现有的 Employee 实体用作父实体。

但请注意,在此示例中,对 pm.makePersistent() 的调用没有使用事务。在没有显式事务的情况下,两个实体都是使用单独的原子操作而创建的。在此情况下,可以成功创建 Employee 实体,但创建 ContactInfo 实体会失败。要确保同时成功创建两个实体或者一个实体都没有创建,必须使用事务:

    Employee e = new Employee();
   
ContactInfo ci = new ContactInfo();
    e
.setContactInfo(ci);

   
try {
       
Transaction tx = pm.currentTransaction();
        tx
.begin();
        pm
.makePersistent(e);
        tx
.commit();
   
} finally {
       
if (tx.isActive()) {
            tx
.rollback();
       
}
   
}

如果两个实体在建立关系之前得以保存,则 App Engine 无法将现有的 ContactInfo 实体“移动”到 Employee 实体的实体组中,因为只有在创建实体时才会分配实体组。App Engine 可以建立带有引用的关系,但相关实体不会在同一组中。在此情况下,无法在同一个事务中更新或删除这两个实体。如果尝试在同一个事务中更新或删除不同组的实体,则将引发 JDOFatalUserException。

保存子对象被修改的父对象时,会将更改保存到子对象。以此方式使父对象保留所有相关子对象的持久性并在保存更改时使用事务是很好的主意。

 

从属子代和级联删除

JDO 的 App Engine 实现使得所有有主关系变成“从属”。如果删除父对象,则也会删除所有子对象。通过对父对象的从属字段赋新的值来打破有主关系也会删除旧的子对象。

和创建、更新对象一样,如果需要在单个原子操作中进行级联删除中的每个删除,则必须执行事务删除。

实体组

每个实体都属于一个实体组,它是可以在一个事务中控制的一组实体(一个或多个)。

实体组关系会让 App Engine 在分布式网络的相同部分中存储若干实体。事务会针对实体组设置数据存储区操作,且所有操作都会以组的形式应用。

如果事务失败,则全都不应用。

当应用程序创建一个实体时,它将另一个实体分配为新实体的父实体。向新实体分配父实体会将新实体放置在与父实体相同的实体组。

没有父实体的实体是根实体。作为另一个实体的父实体的实体也可以有父实体。从某实体到根的父实体链是该实体的路径,路径的成员是该实体的祖先。实体的父实体是在创建该实体时定义的,且以后不能再更改。

每个采用指定根实体作为祖先的实体都在相同的实体组中。一个组中的所有实体都存储在相同的数据存储区节点中。一个事务可以修改一个组中的多个实体,或向组添加新实体(方法是以组中的现有实体作为新实体的父实体)。

JDO 接口的 App Engine 实现使用实体组表示有主的一对一或一对多关系。