Mis favoritos | Español | Acceder

Relaciones

Puedes crear relaciones entre objetos persistentes mediante los campos de los tipos de objetos. Una relación entre objetos persistentes puede ser de propiedad, en la que uno de los objetos no puede existir sin el otro, o sin propiedad, en la que ambos objetos pueden existir independientemente de su relación con el otro. La implementación App Engine de la interfaz JDO puede crear relaciones de propiedad uno a uno y uno a varios, tanto unidireccionales como bidireccionales. Las relaciones sin propiedad aún no admiten una sintaxis natural, pero puedes administrarlas mediante el almacenamiento directo de las claves del almacén de datos en los campos. App Engine crea de forma automática entidades relacionadas en grupos de entidades para admitir la actualización en conjunto de los objetos relacionados, aunque es responsabilidad de la aplicación saber cuándo utilizar las transacciones del almacén de datos.

Relaciones de propiedad uno a uno

Crea una relación de propiedad uno a uno unidireccional entre dos objetos persistentes mediante un campo cuyo tipo sea la clase de la clase relacionada.

El ejemplo que se muestra a continuación define una clase de datos ContactInfo y una clase de datos Employee que tienen una relación uno a uno de Employee a ContactInfo.

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;
    }

    // ...
}

Los objetos persistentes se representan como dos entidades distintas del almacén de datos que contienen dos tipos diferentes. La relación se representa mediante una relación del grupo de entidades: la clave de la entidad secundaria utiliza la clave de la entidad principal como entidad principal del grupo de entidades. Si la aplicación accede al objeto secundario mediante el campo del objeto principal, la implementación de JDO realizará una consulta de la entidad principal del grupo de entidades para obtener el objeto secundario.

La clase secundaria deberá tener un campo de clave cuyo tipo pueda contener la información de la clave de la entidad principal, ya sea una clave o un valor de Key codificado como una cadena. Para obtener información sobre los tipos de campos de clave, consulta Creación de datos: claves.

Crea una relación uno a uno bidireccional mediante los campos de ambas clases, con una anotación en el campo de la clase secundaria para especificar que los campos representan una relación bidireccional. El campo de la clase secundaria deberá contener una anotación @Persistent con el argumento mappedBy = "...", en el que el valor sea el nombre del campo de la clase principal. Si el campo de un objeto está relleno, el campo de referencia correspondiente del otro objeto se rellenará de forma automática.

ContactInfo.java

import Employee;

// ...
    @Persistent(mappedBy = "contactInfo")
    private Employee employee;

Los objetos secundarios se cargan desde el almacén de datos cuando se accede a ellos por primera vez. Si no accedes al objeto secundario de un objeto principal, nunca se cargará la entidad del objeto secundario. (La interfaz del almacén de datos no admite la carga "impaciente" de objetos secundarios. El almacén de datos no admite consultas con Join, de modo que una implementación de carga impaciente no evitará que la aplicación realice una llamada al almacén de datos).

Relaciones de propiedad uno a varios

Para crear una relación uno a varios entre objetos de una clase y varios objetos de otra, utiliza una colección de la clase relacionada:

Employee.java

import java.util.List;

// ...
    @Persistent
    private List<ContactInfo> contactInfoSets;

Una relación bidireccional uno a varios es similar a una relación uno a uno, con un campo de la clase principal que utiliza la anotación @Persistent(mappedBy = "..."), en la que el valor es el nombre del campo de la clase secundaria:

Employee.java

import java.util.List;

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

ContactInfo.java

import Employee;

// ...
    @Persistent
    private Employee employee;

Los tipos de colecciones especificados en Definición de las clases de datos: colecciones admiten las relaciones uno a varios. Sin embargo, los conjuntos no admiten las relaciones uno a varios.

App Engine no admite consultas con Join: no puedes consultar una entidad principal mediante un atributo de una entidad secundaria. (Puedes consultar una propiedad de una clase insertada, ya que las clases insertadas almacenan propiedades en la entidad principal. Consulta Definición de las clases de datos: clases insertadas).

Cómo las colecciones ordenadas mantienen el orden

Las colecciones ordenadas, como, por ejemplo List<...>, mantienen el orden de los objetos al guardar el objeto de la entidad principal. JDO requiere que las bases de datos mantengan este orden mediante el almacenamiento de la posición de cada objeto como una propiedad del objeto. App Engine almacena esto como una propiedad de la entidad correspondiente mediante un nombre de propiedad que es igual al nombre del campo de la entidad principal seguido de _INTEGER_IDX. Las propiedades de posición son poco eficientes. Si se añade, se elimina o se mueve un elemento de la colección, todas las entidades posteriores a la ubicación modificada de la colección deberán actualizarse. Este proceso puede tardar y ser propenso a errores si no se realiza en una transacción.

Si no necesitas mantener un orden arbitrario en una colección, pero debes utilizar un tipo de colección ordenada, puedes especificar una ordenación de acuerdo con las propiedades de los elementos que utilizan una anotación, una extensión a JDO proporcionada por DataNucleus:

import java.util.List;
import javax.jdo.annotations.Extension;
import javax.jdo.annotations.Order;
import javax.jdo.annotations.Persistent;

// ...
    @Persistent
    @Order(extensions = @Extension(vendorName="datanucleus", key="list-ordering", value="state asc, city asc"))
    private List<ContactInfo> contactInfoSets = new List<ContactInfo>();

La anotación @Order (que utiliza la extensión list-ordering) especifica el orden deseado de los elementos de la colección como una cláusula de ordenación JDOQL. La ordenación utiliza valores de propiedades de los elementos. Como ocurre con las consultas, todos los elementos de una colección deben contener valores para las propiedades utilizadas en la cláusula de ordenación.

El acceso a una colección realiza una consulta. Si la cláusula de ordenación de un campo utiliza más de un criterio de ordenación, la consulta requerirá un índice de almacén de datos. Para obtener más información sobre índices, consulta Consultas e índices.

Para una mayor eficiencia, utiliza siempre una cláusula de ordenación explícita para relaciones uno a varios de tipos de colecciones ordenadas, si es posible.

Relaciones sin propiedad

Además de las relaciones de propiedad, el API JDO también ofrece la posibilidad de administrar relaciones sin propiedad. La implementación App Engine de JDO aún no dispone de esta función, pero no te preocupes, porque puedes seguir administrando estas relaciones mediante valores Key en lugar de instancias (o colecciones de instancias) de tus objetos modelo. Puedes considerar la posibilidad de almacenar objetos Key como forma de crear una "clave extranjera" arbitraria entre dos objetos. El almacén de datos no garantiza integridad referencial con estas referencias Key, pero el uso de Key facilita la creación (y posterior extracción) de cualquier relación entre dos objetos. Sin embargo, si sigues esta ruta, deberás tener en cuenta algunas consideraciones adicionales. En primer lugar, es responsabilidad de la aplicación asegurarse de que las claves serán del tipo apropiado (JDO y el compilador no realizarán ninguna comprobación de tipos). En segundo lugar, todos los objetos deberán pertenecer al mismo grupo de entidades para realizar una actualización atómica de los objetos de ambos lados de la relación.

Sugerencia: en algunos casos, es posible que sea necesario crear una relación de propiedad como si fuese una relación sin propiedad. Esto se debe a que todos los objetos de una relación de propiedad se colocan de forma automática en el mismo grupo de entidades y un grupo de entidades sólo admite de 1 a 10 escrituras por segundo. Por ejemplo, si un objeto principal recibe 0,75 escrituras por segundo y un objeto secundario recibe 0,75 escrituras por segundo, se podrá crear una relación sin propiedad para que tanto el objeto principal como el secundario residan en sus propios grupos de entidades independientes.

Relaciones sin propiedad uno a uno

Supongamos que queremos crear un modelo de persona y comida y que la persona sólo puede tener una comida favorita, pero esa comida favorita no le pertenece porque puede ser la comida favorita de una infinidad de personas:

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;

    // ...
}

En este ejemplo, en lugar de asignar a Person un miembro del tipo Food que represente la comida favorita de la persona, asignamos a Person un miembro del tipo Key, en el que Key es el único identificador de un objeto Food. Ten en cuenta que a menos que una instancia de Person y la instancia de Food a las que hace referencia Person.favoriteFood estén en el mismo grupo de entidades, no es posible actualizar ni la persona ni la comida favorita de la persona en una única transacción.

Relaciones sin propiedad uno a varios

Ahora, supongamos que queremos que una persona tenga varias comidas favoritas. De nuevo, una comida favorita no puede pertenecer a una persona porque puede ser la comida favorita de una infinidad de personas:

Person.java

// ... imports ...

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

    @Persistent
    private Set<Key> favoriteFoods;

    // ...
}

En este ejemplo, en lugar de asignar a la persona un miembro del tipo Set<Food> que represente las comidas favoritas de la persona, asignamos a la persona un miembro del tipo Set<Key>, en el que el conjunto contiene los únicos identificadores de objetos Food. Ten en cuenta que a menos que la instancia de Person y una instancia de Food contenidas en Person.favoriteFoods estén en el mismo grupo de entidades, no es posible actualizar ni la persona ni la comida favorita en una única transacción.

Relaciones varios a varios

Podemos crear una relación varios a varios si mantenemos colecciones de claves en ambos lados de la relación. Adaptemos nuestro ejemplo para que Food realice el seguimiento de las personas que la consideran como favorita:

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;

En este ejemplo, Person mantiene un conjunto de valores Key que identifica de forma exclusiva los objetos Food que son favoritos y Food mantiene un conjunto de valores Key que identifica de forma exclusiva los objetos Person que la consideran como favorita.

Al crear una relación varios a varios mediante valores Key, ten en cuenta que es responsabilidad de la aplicación mantener ambos lados de la relación:

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());
}

Ten en cuenta que a menos que la instancia de Person y una instancia de Food contenidas en Person.favoriteFoods estén en el mismo grupo de entidades, no es posible actualizar ni la persona ni la comida favorita en una única transacción. Si no es posible colocar los objetos en el mismo grupo de entidades, la aplicación deberá responsabilizarse de la posibilidad de que las comidas favoritas de una persona se actualicen sin la actualización correspondiente del conjunto de aficionados a la comida, o a la inversa, es decir, que un conjunto de aficionados a la comida se actualice sin la actualización correspondiente del conjunto de comidas favoritas de los aficionados.

Relaciones, grupos de entidades y transacciones

Al guardar un objeto que contiene relaciones de propiedad en el almacén de datos, el resto de objetos que se pueden consultar a través de relaciones y que deben guardarse (objetos nuevos o que se han modificado desde que se cargaron por última vez), se guardarán de forma automática. Esto afecta en gran medida a las transacciones y a los grupos de entidades.

Considera el siguiente ejemplo que utiliza una relación unidireccional entre las clases Employee y ContactInfo anteriores:

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

    pm.makePersistent(e);

Si se guarda el nuevo objeto Employee mediante el método pm.makePersistent(), el nuevo objeto relacionado ContactInfo se guardará de forma automática. Puesto que ambos objetos son nuevos, App Engine crea dos nuevas entidades en el mismo grupo de entidades y utiliza la entidad Employee como entidad principal de la entidad ContactInfo. De igual modo, si el objeto Employee ya se ha guardado y el objeto relacionado ContactInfo es nuevo, App Engine creará la entidad ContactInfo mediante la entidad existente Employee como entidad principal.

Sin embargo, ten en cuenta que la llamada a pm.makePersistent() en este ejemplo no utiliza una transacción. Sin una transacción explícita, ambas entidades se crearán mediante acciones atómicas independientes. En este caso, es posible que la creación de la entidad Employee se realice con éxito, pero que falle la creación de la entidad ContactInfo. Para asegurarse de que ambas entidades se creen con éxito o no se cree ninguna entidad, deberás utilizar una transacción:

    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();
        }
    }

Si ambos objetos se guardan antes de que se establezca la relación, App Engine no podrá "mover" la entidad existente ContactInfo al grupo de entidades de la entidad Employee, ya que los grupos de entidades sólo pueden asignarse al crear las entidades. App Engine puede establecer la relación con una referencia, pero las entidades relacionadas no estarán en el mismo grupo. En este caso, las dos entidades no podrán actualizarse o eliminarse en la misma transacción. Si se intenta actualizar o eliminar entidades de grupos diferentes en la misma transacción, se generará una excepción JDOFatalUserException.

Si se guarda un objeto principal cuyos objetos secundarios se han modificado, se guardarán los cambios realizados a los objetos secundarios. Es recomendable permitir que los objetos principales mantengan la persistencia de esta forma para todos los objetos secundarios relacionados y que utilicen transacciones al guardar cambios.

Objetos secundarios dependientes y eliminaciones en cascada

La implementación App Engine de JDO convierte todas las relaciones de propiedad en "dependientes". Si se elimina un objeto principal, también se eliminarán todos los objetos secundarios. La ruptura de una relación de propiedad debido a la asignación de un nuevo valor al campo dependiente de la entidad principal, también eliminará la entidad secundaria antigua.

Al igual que ocurre con la creación y actualización de objetos, si necesitas realizar todas las eliminaciones de una eliminación en cascada en una única acción atómica, deberás llevar a cabo la eliminación en una transacción.

Nota: la implementación de JDO es la que elimina objetos secundarios dependientes, no el almacén de datos. Si eliminas una entidad principal a través de un API de nivel inferior o de la consola de administración, no se eliminarán los objetos secundarios relacionados.