My favorites | Português | Sign in

Relacionamentos

Você pode modelar relacionamentos entre objetos persistentes usando campos dos tipos de objeto. Um relacionamento entre objetos persistentes pode ser descrito como proprietário, no qual um dos objetos não pode existir sem o outro, ou não proprietário, no qual os dois objetos podem existir independentemente do relacionamento entre si. A implementação da interface JDO do Google App Engine pode modelar relacionamentos proprietários de um-para-um e relacionamentos proprietários de um-para-vários, de maneira uni ou bidirecional. Os relacionamentos não proprietários ainda não são suportados com uma sintaxe natural, mas você mesmo pode gerenciar esses relacionamentos ao armazenar as chaves do armazenamento de dados diretamente nos campos. O Google App Engine cria automaticamente entidades relacionadas em grupos de entidades para oferecer suporte à atualização em conjunto de objetos relacionados. No entanto, é responsabilidade do aplicativo saber quando usar as transações do armazenamento de dados.

Relacionamentos proprietários de um-para-um

É possível criar um relacionamento proprietário de um-para-um unidirecional entre dois objetos persistentes usando um campo cujo tipo é a classe da classe relacionada.

O exemplo a seguir define uma classe de dados ContactInfo e uma classe de dados Employee, com um relacionamento de um-para-um de Employee para 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;
    }

    // ...
}

Os objetos persistentes são representados como duas entidades distintas no armazenamento de dados, com dois tipos diferentes. O relacionamento é representado usando um relacionamento de grupo de entidades: a chave do filho usa a chave do pai como o pai do grupo de entidades. Quando o aplicativo acessa o objeto filho usando o campo do objeto pai, a implementação JDO realiza uma consulta ao pai do grupo de entidades para obter o filho.

A classe filho deve ter um campo de chave cujo tipo possa conter as informações da chave pai: uma Key ou um valor Key codificado como uma string. Consulte Como criar dados: chaves para obter informações sobre os tipos de campos das chaves.

Para criar um relacionamento de um-para-um bidirecional, use os campos das duas classes e inclua uma anotação sobre o campo da classe filho para declarar que os campos representam um relacionamento bidirecional. O campo da classe filho deve ter uma anotação @Persistent com o argumento mappedBy = "...", em que o valor é o nome do campo na classe pai. Se o campo estiver preenchido em um objeto, o campo de referência correspondente no outro objeto será preenchido automaticamente.

ContactInfo.java

import Employee;

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

Os objetos filhos são carregados a partir do armazenamento de dados no primeiro acesso. Se você não acessar o objeto filho em um objeto pai, a entidade do objeto filho nunca será carregada. A interface do armazenamento de dados não oferece suporte ao carregamento "ávido" dos objetos filhos. O armazenamento de dados não oferece suporte à consultas associadas e, portanto, uma implementação do carregamento ávido não evitaria que o aplicativo fizesse uma chamada para o armazenamento de dados.

Relacionamentos proprietários de um-para-vários

Para criar um relacionamento de um-para-vários entre objetos de uma classe e vários objetos de outra, use uma coleção das classes relacionadas:

Employee.java

import java.util.List;

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

Um relacionamento bidirecional de um-para-vários é semelhante ao de um-para-um, com um campo na classe pai usando a anotação @Persistent(mappedBy = "..."), em que o valor é o nome do campo na classe filho:

Employee.java

import java.util.List;

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

ContactInfo.java

import Employee;

// ...
    @Persistent
    private Employee employee;

Os tipos de coleção listados em Como definir classes de dados: coleções oferecem suporte aos relacionamentos de um-para-vários. No entanto, as matrizes não oferecem suporte a esses relacionamentos.

O Google App Engine não suporta consultas associadas: não é possível consultar uma entidade pai usando um atributo de uma entidade filho. Você pode consultar uma propriedade de uma classe incorporada, pois as classes incorporadas armazenam as propriedades na entidade pai. Consulte Como definir classes de dados: classes incorporadas.

Como as coleções ordenadas mantêm a sua ordem

As coleções ordenadas, como List<...>, preservam a ordem dos objetos quando o objeto pai é salvo. A JDO exige que os bancos de dados preservem essa ordem armazenando a posição de cada objeto como uma propriedade do objeto. O Google App Engine armazena essa propriedade como uma propriedade da entidade correspondente, usando um nome de propriedade igual ao do campo do pai seguido por _INTEGER_IDX. As propriedades de posicionamento não são eficientes. Se um elemento for adicionado, removido ou movido na coleção, todas as entidades subsequentes no local modificado devem ser atualizadas. Se realizada fora de uma transação, essa ação pode ser lenta e sujeita a erros.

Se não for necessário preservar uma ordem arbitrária em uma coleção, mas for preciso usar um tipo de coleção ordenada, é possível especificar uma ordenação com base nas propriedades dos elementos usando uma anotação, uma extensão da JDO fornecida pelo 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>();

A anotação @Order (usando a extensão list-ordering) especifica a ordem desejada dos elementos da coleção como uma cláusula de ordenação JDOQL. A ordenação usa valores de propriedades dos elementos. Assim como acontece com as consultas, todos os elementos de uma coleção devem ter valores para as propriedades usadas na cláusula de ordenação.

O acesso a uma coleção realiza uma consulta. Se a cláusula de ordenação de um campo usa mais de uma ordem de classificação, a consulta solicita um índice do armazenamento de dados. Consulte Consultas e índices para obter mais informações sobre índices.

Para obter mais eficiência, use sempre que possível uma cláusula de ordenação explícita para relacionamentos de um-para-vários de tipos de coleções ordenadas.

Relacionamentos não proprietários

Além dos relacionamentos proprietários, a API da JDO também fornece um recurso para o gerenciamento dos relacionamentos não proprietários. A implementação do Google App Engine da JDO ainda não implementa esse recurso, mas fique tranquilo, ainda é possível gerenciar esses relacionamentos usando valores Key em vez de instâncias (ou coleções de instâncias) dos seus objetos de modelo. Você pode pensar no armazenamento de objetos Key como a modelagem de uma "chave externa" arbitrária entre dois objetos. O armazenamento de dados não garante a integridade referencial com essas referências Key, mas o uso de Key facilita a modelagem (e, em seguida, a obtenção) de qualquer relacionamento entre dois objetos. No entanto, é preciso lembrar de algumas outras coisas antes de ir por esse caminho. Em primeiro lugar, é responsabilidade do aplicativo certificar-se que as Keys são do tipo adequado. A JDO e o compilador não farão isso por você. Em segundo lugar, todos os objetos devem pertencer ao mesmo grupo de entidades para realizar uma atualização atômica dos objetos nos dois lados do relacionamento.

Dica: Em alguns casos pode ser necessário modelar um relacionamento proprietário como se fosse não proprietário. Isso se deve ao fato de que todos os objetos envolvidos em um relacionamento proprietário são colocados automaticamente no mesmo grupo de entidades, e um grupo de entidades pode suportar apenas de uma a dez gravações por segundo. Por exemplo, se um objeto pai está recebendo .75 gravações por segundo e um objeto filho está recebendo .75 gravações por segundo, pode fazer sentido modelar esse relacionamento como não proprietário para que tanto o pai quanto o filho residam sozinhos, independentes dos grupos de entidades.

Relacionamentos não proprietários de um-para-um

Imagine que queremos modelar pessoa e comida, em que uma pessoa pode ter apenas uma comida favorita, mas uma comida favorita não pertence à pessoa porque pode ser a comida favorita de várias outras:

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;

    // ...
}

Nesse exemplo, em vez de dar à Person um membro de tipo Food para representar a comida favorita da pessoa, demos à Person um membro de tipo Key, em que Key é o identificador exclusivo de um objeto Food. Observe que a menos que uma instância de Person e a instância de Food referidas por Person.favoriteFood estejam no mesmo grupo de entidades, não é possível atualizar a pessoa e a comida favorita dela em uma única transação.

Relacionamentos não proprietários de um-para-vários

Agora, imagine que queremos deixar uma pessoa ter várias comidas favoritas. Novamente, uma comida favorita não pertence à pessoa porque pode ser a comida favorita de várias outras:

Person.java

// ... imports ...

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

    @Persistent
    private Set<Key> favoriteFoods;

    // ...
}

Nesse exemplo, em vez de dar à Person um membro de tipo Set<Food> para representar as comidas favoritas da pessoa, demos à Person um membro de tipo Set<Key>, em que o conjunto contém os identificadores exclusivos de objetos Food. Observe que a menos que uma instância de Person e uma instância de Food contidas em Person.favoriteFoods estejam no mesmo grupo de entidades, não é possível atualizar a pessoa e a comida favorita em uma única transação.

Relacionamentos de vários-para-vários

Podemos modelar um relacionamento de vários-para-vários ao mantermos coleções de chaves nos dois lados do relacionamento. Vamos ajustar o nosso exemplo para deixar Food rastrear as pessoas que a consideram a sua comida 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;

Nesse exemplo, Person mantém um conjunto de valores Key que identificam exclusivamente os objetos Food que são favoritos, e Food mantém um conjunto de valores Key que identificam exclusivamente os objetos Person que a consideram favorita.

Ao modelar um relacionamento de vários-para-vários usando valores Key, saiba que é responsabilidade do aplicativo manter os dois lados do relacionamento:

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

Observe que a menos que uma instância de Person e uma instância de Food contidas em Person.favoriteFoods estejam no mesmo grupo de entidades, não é possível atualizar a pessoa e a comida favorita em uma única transação. Se não for possível colocar os objetos no mesmo grupo de entidades, o aplicativo deve considerar a possibilidade de que as comidas favoritas de uma pessoa sejam atualizadas sem a atualização correspondente do conjunto de fãs das comidas ou, por outro lado, que o conjunto de fãs das comidas seja atualizado sem a atualização correspondente do conjunto de comidas favoritas dos fãs.

Relacionamentos, grupos de entidades e transações

Quando um objeto com relacionamentos proprietários é salvo no armazenamento de dados, todos os outros objetos que podem ser alcançados por meio dos relacionamentos e precisam ser salvos (são novos ou foram modificados desde o último carregamento) são salvos automaticamente. Isso gera implicações importantes para as transações e os grupos de entidades.

Considere o exemplo a seguir usando um relacionamento unidirecional entre as classes Employee e ContactInfo acima:

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

    pm.makePersistent(e);

Quando o objeto Employee novo é salvo usando o método pm.makePersistent(), o novo objeto ContactInfo relacionado é salvo automaticamente. Como os dois objetos são novos, o Google App Engine cria duas entidades novas no mesmo grupo de entidades, usando a entidade Employee como o pai da entidade ContactInfo. Da mesma maneira, se o objeto Employee já foi salvo e o objeto ContactInfo relacionado for novo, o Google App Engine cria a entidade ContactInfo usando a entidade Employee existente como o pai.

Observe, no entanto, que a chamada para pm.makePersistent() nesse exemplo não usa uma transação. Sem uma transação explícita, as duas entidades são criadas usando ações atômicas separadas. Nesse caso, é possível que a entidade Employee seja criada com sucesso, mas o mesmo não acontecerá com a criação da entidade ContactInfo. Para garantir que as duas entidades sejam criadas com sucesso ou que nenhuma entidade seja criada, use uma transação:

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

Se os dois objetos foram salvos antes de se estabelecer o relacionamento, o Google App Engine não poderá "mover" a entidade ContactInfo existente para grupo de entidades da entidade Employee, pois os grupos de entidades só podem ser atribuídos na criação das entidades. O Google App Engine pode estabelecer o relacionamento com uma referência, mas as entidades relacionadas não estarão no mesmo grupo. Nesse caso, as duas entidades não podem ser atualizadas nem excluídas na mesma transação. Uma tentativa de atualizar ou excluir identidades de grupos diferentes na mesma transação irá gerar uma JDOFatalUserException.

Salvar um objeto pai cujos objetos filhos foram modificados salvará as alterações nos objetos filho. É uma boa ideia permitir que os objetos pai mantenham a persistência de todos os objetos filho relacionados dessa maneira e usem as transações ao salvar as alterações.

Filhos dependentes e exclusões em cascata

A implementação do Google App Engine da JDO torna todos os relacionamentos proprietários "dependentes". Se um objeto pai é excluído, todos os objetos filho também o serão. Interromper um relacionamento proprietário pela atribuição de um valor novo ao campo dependente no pai também excluirá o filho antigo.

Assim como acontece ao criar e atualizar objetos, se for necessário que cada exclusão em uma exclusão em cascata ocorra em uma única ação atômica, realize a exclusão em uma transação.

Observação: A implementação JDO faz o trabalho de exclusão dos objetos filho dependentes, não o armazenamento de dados. Se você excluir uma entidade pai usando a API de nível inferior ou o Console de administração, os objetos filho relacionados não serão excluídos.