O que é "o lado inverso da associação" em uma associação JPA OneToMany / ManyToOne bidirecional?

167

Na seção de exemplo da @OneToManyreferência de anotação JPA :

Exemplo 1-59 @OneToMany - Classe de cliente com genéricos

@Entity
public class Customer implements Serializable {
    ...
    @OneToMany(cascade=ALL, mappedBy="customer")
    public Set<Order> getOrders() { 
        return orders; 
    }
    ...
}

Exemplo 1-60 @ManyToOne - Classe de ordem com genéricos

@Entity
public class Order implements Serializable {
    ...
    @ManyToOne
    @JoinColumn(name="CUST_ID", nullable=false)
    public Customer getCustomer() { 
        return customer; 
    }
    ...
}

Parece-me que a Customerentidade é a proprietária da associação. No entanto, na explicação para o mappedByatributo no mesmo documento, está escrito que:

se o relacionamento for bidirecional, defina o elemento mappedBy no lado inverso (não proprietário) da associação como o nome do campo ou propriedade que possui o relacionamento, como mostra o Exemplo 1-60.

No entanto, se não estou errado, parece que no exemplo, o mappedByé realmente especificado no lado proprietário da associação, em vez do lado não proprietário.

Então, minha pergunta é basicamente:

  1. Em uma associação bidirecional (um para muitos / muitos para um), qual das entidades é o proprietário? Como podemos designar o lado Único como proprietário? Como podemos designar o lado Muitos como o proprietário?

  2. O que se entende por "o lado inverso da associação"? Como podemos designar o lado Um como o inverso? Como podemos designar o lado Muitos como o inverso?

Behrang Saeedzadeh
fonte
1
o link que você forneceu está desatualizado. Por favor atualize.
precisa saber é o seguinte

Respostas:

306

Para entender isso, você deve dar um passo atrás. No OO, o cliente possui os pedidos (os pedidos são uma lista no objeto do cliente). Não pode haver um pedido sem um cliente. Portanto, o cliente parece ser o proprietário dos pedidos.

Mas no mundo do SQL, um item realmente conterá um ponteiro para o outro. Como há 1 cliente para N pedidos, cada pedido contém uma chave estrangeira para o cliente ao qual ele pertence. Essa é a "conexão" e significa que a ordem "possui" (ou contém literalmente) a conexão (informações). Isso é exatamente o oposto do mundo OO / modelo.

Isso pode ajudar a entender:

public class Customer {
     // This field doesn't exist in the database
     // It is simulated with a SQL query
     // "OO speak": Customer owns the orders
     private List<Order> orders;
}

public class Order {
     // This field actually exists in the DB
     // In a purely OO model, we could omit it
     // "DB speak": Order contains a foreign key to customer
     private Customer customer;
}

O lado inverso é o OO "proprietário" do objeto, neste caso o cliente. O cliente não possui colunas na tabela para armazenar os pedidos, portanto, é necessário informar onde ele pode salvar esses dados na tabela de pedidos (o que acontece via mappedBy).

Outro exemplo comum são as árvores com nós, que podem ser pais e filhos. Nesse caso, os dois campos são usados ​​em uma classe:

public class Node {
    // Again, this is managed by Hibernate.
    // There is no matching column in the database.
    @OneToMany(cascade = CascadeType.ALL) // mappedBy is only necessary when there are two fields with the type "Node"
    private List<Node> children;

    // This field exists in the database.
    // For the OO model, it's not really necessary and in fact
    // some XML implementations omit it to save memory.
    // Of course, that limits your options to navigate the tree.
    @ManyToOne
    private Node parent;
}

Isso explica as obras de design muitos-para-um da "chave estrangeira". Existe uma segunda abordagem que usa outra tabela para manter as relações. Isso significa que, para o nosso primeiro exemplo, você tem três tabelas: aquela com clientes, aquela com pedidos e uma tabela de duas colunas com pares de chaves primárias (customerPK, orderPK).

Essa abordagem é mais flexível que a acima (ela pode lidar facilmente com um para um, muitos para um, um para muitos e até muitos para muitos). O preço é esse

  • é um pouco mais lento (ter que manter outra tabela e unir usa três tabelas em vez de apenas duas),
  • a sintaxe de junção é mais complexa (o que pode ser entediante se você precisar escrever muitas consultas manualmente, por exemplo, ao tentar depurar algo)
  • é mais suscetível a erros porque, de repente, você pode obter muitos ou poucos resultados quando algo der errado no código que gerencia a tabela de conexões.

É por isso que eu raramente recomendo essa abordagem.

Aaron Digulla
fonte
36
Apenas para esclarecer: o lado oposto é o dono; o lado é o inverso. Você não tem escolha (praticamente falando).
John John
11
Não, o Hibernate inventou isso. Não gosto, pois expõe parte da implementação ao modelo OO. Eu prefiro um @Parentou @Childanotação em vez de "XtoY" para o estado que os de conexão meios (em vez de como ele é implementado )
Aaron Digulla
4
@AaronDigulla toda vez que tenho que passar por um mapeamento OneToMany, vim ler esta resposta, provavelmente a melhor sobre o assunto no SO.
Eugene
7
Uau. Se apenas a documentação das estruturas ORM tivesse uma explicação tão boa - isso tornaria tudo mais fácil de engolir! Excelente resposta!
NickJ
2
@klausch: A documentação do Hibernate é confusa. Ignore isto. Veja o código, o SQL no banco de dados e como as chaves estrangeiras funcionam. Se quiser, leve um pouco de sabedoria para casa: a documentação é uma mentira. Use a fonte, Luke.
Aaron Digulla 15/09/2015
41

Inacreditavelmente, em três anos, ninguém respondeu à sua excelente pergunta com exemplos das duas maneiras de mapear o relacionamento.

Conforme mencionado por outros, o lado "proprietário" contém o ponteiro (chave estrangeira) no banco de dados. Você pode designar ambos os lados como proprietário, no entanto, se você designar o lado Único como proprietário, o relacionamento não será bidirecional (o lado inverso, conhecido como "muitos", não terá conhecimento de seu "proprietário"). Isso pode ser desejável para encapsulamento / acoplamento solto:

// "One" Customer owns the associated orders by storing them in a customer_orders join table
public class Customer {
    @OneToMany(cascade = CascadeType.ALL)
    private List<Order> orders;
}

// if the Customer owns the orders using the customer_orders table,
// Order has no knowledge of its Customer
public class Order {
    // @ManyToOne annotation has no "mappedBy" attribute to link bidirectionally
}

A única solução de mapeamento bidirecional é ter o lado "many" como ponteiro para o "one" e usar o atributo @OneToMany "mappedBy". Sem o atributo "mappedBy", o Hibernate esperará um mapeamento duplo (o banco de dados terá a coluna de junção e a tabela de junção, que é redundante (geralmente indesejável)).

// "One" Customer as the inverse side of the relationship
public class Customer {
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "customer")
    private List<Order> orders;
}

// "many" orders each own their pointer to a Customer
public class Order {
    @ManyToOne
    private Customer customer;
}
Steve Jones
fonte
2
No seu exemplo unidirecional, o JPA espera que exista uma tabela customer_orders extra. Com o JPA2, você pode usar a anotação @JoinColumn (que pareço usar com frequência) no campo de pedidos do Cliente para indicar a coluna de chave estrangeira do banco de dados na tabela Pedido que deve ser usada. Dessa forma, você tem uma relação unidirecional em Java enquanto ainda possui uma coluna de chave estrangeira na tabela Ordem. Portanto, no mundo Objeto, o Pedido não sabe sobre o Cliente, enquanto no mundo do banco de dados, o Cliente não sabe sobre o Pedido.
Henno Vermeulen
1
Para ser super completo, você pode mostrar o caso bidirecional em que o cliente é o proprietário do relacionamento.
HDave
35

A entidade que possui a tabela com chave estrangeira no banco de dados é a entidade proprietária e a outra tabela, sendo apontada, é a entidade inversa.

Venu
fonte
30
ainda mais simples: Proprietário é a tabela com a coluna FK
jacktrades
2
Explicação simples e boa. Qualquer lado pode ser feito Proprietário. Se usarmos mappedBy em Order.java, no campo Cliente <Remover mappedby de Customer.java>, uma nova tabela será criada, algo como Order_Customer, que terá 2 colunas. ORDER_ID e CUSTOMER_ID.
precisa saber é o seguinte
14

Regras simples de relacionamentos bidirecionais:

1.Para relacionamentos bidirecionais muitos-para-um, o lado muitos é sempre o lado proprietário do relacionamento. Exemplo: 1 quarto tem muitas pessoas (uma pessoa pertence apenas a um quarto) -> o lado proprietário é a pessoa

2.Para relacionamentos bidirecionais um para um, o lado proprietário corresponde ao lado que contém a chave estrangeira correspondente.

3.Para relacionamentos bidirecionais muitos para muitos, ambos os lados podem ser o proprietário.

A esperança pode ajudá-lo.

Ken Block
fonte
Por que precisamos ter um proprietário e um inverso? Já temos os conceitos significativos de um lado e de muitos e não importa quem é o proprietário em situações muitos-para-muitos. Quais são as consequências da decisão? É difícil acreditar que alguém com o cérebro esquerdo como um engenheiro de banco de dados decidiu cunhar esses conceitos redundantes.
Dan Cancro 20/03
3

Para duas Classes de Entidade Cliente e Pedido, o hibernate criará duas tabelas.

Casos possíveis:

  1. mappedBy não é usado nas classes Customer.java e Order.java;

    No lado do cliente, será criada uma nova tabela [name = CUSTOMER_ORDER], que manterá o mapeamento de CUSTOMER_ID e ORDER_ID. Essas são chaves primárias das tabelas de clientes e pedidos. No lado do pedido, é necessária uma coluna adicional para salvar o mapeamento de registro Customer_ID correspondente.

  2. mappedBy é usado em Customer.java [Conforme fornecido na declaração do problema] Agora, a tabela adicional [CUSTOMER_ORDER] não é criada. Apenas uma coluna na tabela de pedidos

  3. mappedby é usado em Order.java Agora, uma tabela adicional será criada pelo hibernate. [name = CUSTOMER_ORDER] A tabela de pedidos não terá a coluna adicional [Customer_ID] para o mapeamento.

Qualquer lado pode ser nomeado proprietário do relacionamento. Mas é melhor escolher o lado xxxToOne.

Efeito de codificação -> Somente o lado proprietário da entidade pode alterar o status do relacionamento. No exemplo abaixo, a classe BoyFriend é proprietária do relacionamento. mesmo se namorada quiser terminar, ela não pode.

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "BoyFriend21")
public class BoyFriend21 {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Boy_ID")
    @SequenceGenerator(name = "Boy_ID", sequenceName = "Boy_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
    private Integer id;

    @Column(name = "BOY_NAME")
    private String name;

    @OneToOne(cascade = { CascadeType.ALL })
    private GirlFriend21 girlFriend;

    public BoyFriend21(String name) {
        this.name = name;
    }

    public BoyFriend21() {
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public BoyFriend21(String name, GirlFriend21 girlFriend) {
        this.name = name;
        this.girlFriend = girlFriend;
    }

    public GirlFriend21 getGirlFriend() {
        return girlFriend;
    }

    public void setGirlFriend(GirlFriend21 girlFriend) {
        this.girlFriend = girlFriend;
    }
}

import org.hibernate.annotations.*;
import javax.persistence.*;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.util.ArrayList;
import java.util.List;

@Entity 
@Table(name = "GirlFriend21")
public class GirlFriend21 {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "Girl_ID")
    @SequenceGenerator(name = "Girl_ID", sequenceName = "Girl_ID_SEQUENCER", initialValue = 10,allocationSize = 1)
    private Integer id;

    @Column(name = "GIRL_NAME")
    private String name;

    @OneToOne(cascade = {CascadeType.ALL},mappedBy = "girlFriend")
    private BoyFriend21 boyFriends = new BoyFriend21();

    public GirlFriend21() {
    }

    public GirlFriend21(String name) {
        this.name = name;
    }


    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public GirlFriend21(String name, BoyFriend21 boyFriends) {
        this.name = name;
        this.boyFriends = boyFriends;
    }

    public BoyFriend21 getBoyFriends() {
        return boyFriends;
    }

    public void setBoyFriends(BoyFriend21 boyFriends) {
        this.boyFriends = boyFriends;
    }
}


import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.Arrays;

public class Main578_DS {

    public static void main(String[] args) {
        final Configuration configuration = new Configuration();
         try {
             configuration.configure("hibernate.cfg.xml");
         } catch (HibernateException e) {
             throw new RuntimeException(e);
         }
        final SessionFactory sessionFactory = configuration.buildSessionFactory();
        final Session session = sessionFactory.openSession();
        session.beginTransaction();

        final BoyFriend21 clinton = new BoyFriend21("Bill Clinton");
        final GirlFriend21 monica = new GirlFriend21("monica lewinsky");

        clinton.setGirlFriend(monica);
        session.save(clinton);

        session.getTransaction().commit();
        session.close();
    }
}

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import java.util.List;

public class Main578_Modify {

    public static void main(String[] args) {
        final Configuration configuration = new Configuration();
        try {
            configuration.configure("hibernate.cfg.xml");
        } catch (HibernateException e) {
            throw new RuntimeException(e);
        }
        final SessionFactory sessionFactory = configuration.buildSessionFactory();
        final Session session1 = sessionFactory.openSession();
        session1.beginTransaction();

        GirlFriend21 monica = (GirlFriend21)session1.load(GirlFriend21.class,10);  // Monica lewinsky record has id  10.
        BoyFriend21 boyfriend = monica.getBoyFriends();
        System.out.println(boyfriend.getName()); // It will print  Clinton Name
        monica.setBoyFriends(null); // It will not impact relationship

        session1.getTransaction().commit();
        session1.close();

        final Session session2 = sessionFactory.openSession();
        session2.beginTransaction();

        BoyFriend21 clinton = (BoyFriend21)session2.load(BoyFriend21.class,10);  // Bill clinton record

        GirlFriend21 girlfriend = clinton.getGirlFriend();
        System.out.println(girlfriend.getName()); // It will print Monica name.
        //But if Clinton[Who owns the relationship as per "mappedby" rule can break this]
        clinton.setGirlFriend(null);
        // Now if Monica tries to check BoyFriend Details, she will find Clinton is no more her boyFriend
        session2.getTransaction().commit();
        session2.close();

        final Session session3 = sessionFactory.openSession();
        session1.beginTransaction();

        monica = (GirlFriend21)session3.load(GirlFriend21.class,10);  // Monica lewinsky record has id  10.
        boyfriend = monica.getBoyFriends();

        System.out.println(boyfriend.getName()); // Does not print Clinton Name

        session3.getTransaction().commit();
        session3.close();
    }
}
HakunaMatata
fonte
1

Relacionamentos de tabela vs. relacionamentos de entidade

Em um sistema de banco de dados relacional, pode haver apenas três tipos de relacionamentos de tabela:

  • um para muitos (por meio de uma coluna Chave estrangeira)
  • individual (por meio de uma chave primária compartilhada)
  • muitos-para-muitos (através de uma tabela de links com duas chaves estrangeiras referenciando duas tabelas pai separadas)

Portanto, um one-to-manyrelacionamento de tabela é o seguinte:

relacionamento da tabela <code> um para muitos </code>

Observe que o relacionamento é baseado na coluna Chave estrangeira (por exemplo, post_id) na tabela filha.

Portanto, existe uma única fonte de verdade quando se trata de gerenciar um one-to-manyrelacionamento de tabela.

Agora, se você usar um relacionamento de entidade bidirecional que mapeie o one-to-manyrelacionamento de tabela que vimos anteriormente:

Associação bidirecional da entidade <code> One-To-Many </code>

Se você der uma olhada no diagrama acima, poderá ver que existem duas maneiras de gerenciar esse relacionamento.

Na Postentidade, você tem a commentscoleção:

@OneToMany(
    mappedBy = "post",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();

E, no PostComment, a postassociação é mapeada da seguinte maneira:

@ManyToOne(
    fetch = FetchType.LAZY
)
@JoinColumn(name = "post_id")
private Post post;

Portanto, você tem dois lados que podem alterar a associação da entidade:

  • Ao adicionar uma entrada na commentscoleção filha, uma nova post_commentlinha deve ser associada à postentidade pai por meio de sua post_idcoluna.
  • Ao definir a postpropriedade da PostCommententidade, a post_idcoluna também deve ser atualizada.

Como existem duas maneiras de representar a coluna Chave Externa, você deve definir qual é a fonte da verdade quando se trata de converter a alteração do estado da associação em sua modificação equivalente no valor da coluna Chave Externa.

MappedBy (também conhecido como lado inverso)

O mappedByatributo informa que o @ManyToOnelado é responsável pelo gerenciamento da coluna Chave Externa, e a coleção é usada apenas para buscar as entidades filhas e fazer cascata de alterações no estado da entidade pai para filhos (por exemplo, remover o pai também deve remover as entidades filho).

É chamado de lado inverso porque faz referência à propriedade da entidade filha que gerencia esse relacionamento de tabela.

Sincronize os dois lados de uma associação bidirecional

Agora, mesmo se você definiu o mappedByatributo e a @ManyToOneassociação do lado filho gerencia a coluna Chave Externa, ainda é necessário sincronizar os dois lados da associação bidirecional.

A melhor maneira de fazer isso é adicionar esses dois métodos utilitários:

public void addComment(PostComment comment) {
    comments.add(comment);
    comment.setPost(this);
}

public void removeComment(PostComment comment) {
    comments.remove(comment);
    comment.setPost(null);
}

Os métodos addCommente removeCommentgarantem que ambos os lados estejam sincronizados. Portanto, se adicionarmos uma entidade filha, a entidade filha precisará apontar para o pai e a entidade pai deverá ter o filho contido na coleção filho.

Para obter mais detalhes sobre a melhor maneira de sincronizar todos os tipos de associação de entidade bidirecional, consulte este artigo .

Vlad Mihalcea
fonte