Alguém pode explicar mappedBy no JPA e no Hibernate?

174

Eu sou novo em hibernar e preciso usar relações um para muitos e muitos para um. É um relacionamento bidirecional em meus objetos, para que eu possa atravessar de qualquer direção. mappedByé a maneira recomendada de fazer isso, no entanto, eu não conseguia entender. Alguém pode explicar:

  • qual é a maneira recomendada de usá-lo?
  • que finalidade isso resolve?

Para o meu exemplo, aqui estão as minhas aulas com anotações:

  • Airline POSSUI muitos AirlineFlights
  • Muitos AirlineFlights pertencem a UM Airline

Companhia aérea :

@Entity 
@Table(name="Airline")
public class Airline {
    private Integer idAirline;
    private String name;

    private String code;

    private String aliasName;
    private Set<AirlineFlight> airlineFlights = new HashSet<AirlineFlight>(0);

    public Airline(){}

    public Airline(String name, String code, String aliasName, Set<AirlineFlight> flights) {
        setName(name);
        setCode(code);
        setAliasName(aliasName);
        setAirlineFlights(flights);
    }

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="IDAIRLINE", nullable=false)
    public Integer getIdAirline() {
        return idAirline;
    }

    private void setIdAirline(Integer idAirline) {
        this.idAirline = idAirline;
    }

    @Column(name="NAME", nullable=false)
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = DAOUtil.convertToDBString(name);
    }

    @Column(name="CODE", nullable=false, length=3)
    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = DAOUtil.convertToDBString(code);
    }

    @Column(name="ALIAS", nullable=true)
    public String getAliasName() {
        return aliasName;
    }
    public void setAliasName(String aliasName) {
        if(aliasName != null)
            this.aliasName = DAOUtil.convertToDBString(aliasName);
    }

    @OneToMany(fetch=FetchType.LAZY, cascade = {CascadeType.ALL})
    @JoinColumn(name="IDAIRLINE")
    public Set<AirlineFlight> getAirlineFlights() {
        return airlineFlights;
    }

    public void setAirlineFlights(Set<AirlineFlight> flights) {
        this.airlineFlights = flights;
    }
}

Linhas aéreas:

@Entity
@Table(name="AirlineFlight")
public class AirlineFlight {
    private Integer idAirlineFlight;
    private Airline airline;
    private String flightNumber;

    public AirlineFlight(){}

    public AirlineFlight(Airline airline, String flightNumber) {
        setAirline(airline);
        setFlightNumber(flightNumber);
    }

    @Id
    @GeneratedValue(generator="identity")
    @GenericGenerator(name="identity", strategy="identity")
    @Column(name="IDAIRLINEFLIGHT", nullable=false)
    public Integer getIdAirlineFlight() {
        return idAirlineFlight;
    }
    private void setIdAirlineFlight(Integer idAirlineFlight) {
        this.idAirlineFlight = idAirlineFlight;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="IDAIRLINE", nullable=false)
    public Airline getAirline() {
        return airline;
    }
    public void setAirline(Airline airline) {
        this.airline = airline;
    }

    @Column(name="FLIGHTNUMBER", nullable=false)
    public String getFlightNumber() {
        return flightNumber;
    }
    public void setFlightNumber(String flightNumber) {
        this.flightNumber = DAOUtil.convertToDBString(flightNumber);
    }
}

EDITAR:

Esquema do banco de dados:

AirlineFlights possui o idAirline como ForeignKey e Airline não possui o idAirlineFlights. Isso faz com que o AirlineFlights seja o proprietário / entidade de identificação?

Teoricamente, eu gostaria que a companhia aérea fosse a proprietária da companhia aérea.

insira a descrição da imagem aqui

brainydexter
fonte

Respostas:

150

Ao especificar os @JoinColumndois modelos, você não tem um relacionamento bidirecional. Você tem dois relacionamentos de mão única e um mapeamento muito confuso disso. Você está dizendo aos dois modelos que eles "possuem" a coluna IDAIRLINE. Realmente apenas um deles deveria! A coisa "normal" é retirar @JoinColumncompletamente o @OneToManylado e adicionar mappedBy ao arquivo @OneToMany.

@OneToMany(cascade = CascadeType.ALL, mappedBy="airline")
public Set<AirlineFlight> getAirlineFlights() {
    return airlineFlights;
}

Isso diz ao Hibernate "Vá examinar a propriedade do bean denominada 'companhia aérea' na coisa que eu tenho uma coleção de para encontrar a configuração".

Affe
fonte
1
No final, estou um pouco confuso com sua descrição sobre mappedBy. Importa como as coisas são organizadas em db? @DB: AirlineFlights possui a idAirline como chave estrangeira. A companhia aérea apenas possui idAirline como chave primária e não mantém informações sobre o AirlineFlights @ DB.
brainydexter
10
Sim, isso importa. O nome em mappedBy está dizendo ao Hibernate onde encontrar a configuração para o JoinColumn. (No método getAirline () do AirlineFlight.) Como você o mapeou, colocando o JoinColumn na linha aérea, você está dizendo à Airline que é responsável por manter os valores na outra tabela. É possível dizer a uma entidade que "possui" uma coluna em uma tabela diferente e é responsável por atualizá-la. Geralmente não é algo que você deseja fazer e pode causar problemas com a ordem em que as instruções SQL são executadas.
Macaco
Por favor, veja a edição. No nível do banco de dados, a tabela airlinesFlight possui idAirline como uma coluna de chave estrangeira. Portanto, o JoinColumn deve ser colocado na classe / tabela airlinesFlight no ManytoOne correspondente, já que ele possui essa coluna?
brainydexter
Sim, eu recomendaria fazê-lo dessa maneira. É a opção menos complicada e você parece não precisar de mais nada.
Macaco
"tire o @JoinColumn do lado @OneToMany inteiramente" você quer dizer do @ManyToOnelado, certo?
Nbro 11/04
284

Os sinais MappedBy hibernam que a chave do relacionamento está do outro lado.

Isso significa que, embora você vincule duas tabelas, apenas uma dessas tabelas possui uma restrição de chave estrangeira para a outra. MappedBy permite que você ainda faça um link da tabela que não contém a restrição para a outra tabela.

Kurt Du Bois
fonte
3
você pode esclarecer um pouco mais?
Alexander Suraphel
1
@Kurt Du Bois, por que você usaria, em mappedByvez de definir, uma bidirecional (com restrições de chave estrangeira em cada lado)?
Kevin Meredith
6
Porque às vezes simplesmente não faz sentido colocar uma chave nos dois lados. Digamos, por exemplo, que você tenha uma empresa e um portátil. Um portátil pertencerá apenas a uma empresa, mas uma empresa terá vários portáteis.
Kurt Du Bois
Desculpe o editor por reverter minha resposta, mas na verdade não havia valor agregado em sua edição. A última frase nem fazia sentido.
Kurt Du Bois
O @KurtDuBois, assim mapeado, aparece apenas na imagem de como criar seu banco de dados, ou seja, você está usando o mappedby ou não o hibernate no lado java se comporta de maneira semelhante.
22

mappedbyfala por si, diz ao hibernate para não mapear este campo. já está mapeado por este campo [name = "field"].
campo está na outra entidade (name of the variable in the class not the table in the database)..

Se você não fizer isso, o hibernate mapeará essas duas relações, pois não é a mesma relação

portanto, precisamos dizer ao hibernate para fazer o mapeamento apenas de um lado e coordenar entre eles.

Charif DZ
fonte
é mappedBy é opcional? Porque sem usar o mappedBy, estou obtendo o mesmo resultado, ou seja, mapeamento de objeto bidirecional
Freelancer
você não pode usar on2many e many2one sem usar mappedBy em um dos lados mesma coisa para many2many você tem que usar mappedBy em um lado
Charif DZ
Obrigado por apontar o significado do valor do atributo, que é o nome do campo na outra tabela.
5132 Gab Gabinete
2
Talvez hibernação nem sempre falam por si, mas quando o faz, pelo menos, ele usa pontuação
Amalgovinus
1
Para mim, não fala por si; por outro lado, é muito confuso. Basta olhar para a quantidade de perguntas sobre o que realmente é mappedBye inversedBy. Outros ORMs usar muito mais inteligentes belongsToMany, hasManyatributos.
Jan Bodnar
12

mappedby = "objeto de entidade da mesma classe criada em outra classe”

Nota: -Mapped by pode ser usado apenas em uma classe porque uma tabela deve conter restrição de chave estrangeira. se mapeado por puder ser aplicado em ambos os lados, ele removerá a chave estrangeira da tabela e sem a chave estrangeira, não haverá relação entre duas tabelas.

Nota: - pode ser usado para as seguintes anotações: - 1. @ OneTone 2. @ OneToMany 3. @ ManyToMany

Nota --- Não pode ser usado para a seguinte anotação: - 1. @ ManyToOne

Em um para um: - Execute em qualquer lado do mapeamento, mas em apenas um lado. Ele removerá a coluna extra de restrição de chave estrangeira na tabela em que classe é aplicada.

Por exemplo. Se aplicarmos mapeado por na classe Employee no objeto employee, a chave estrangeira da tabela Employee será removida.

ks gujjar
fonte
12

Relacionamento de tabela vs. relacionamento de entidade

Em um sistema de banco de dados relacional, 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 de entidade bidirecional <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;

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

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).

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
0

Você começou com o mapeamento ManyToOne e, em seguida, colocou o mapeamento OneToMany para o modo bidirecional. Então, no lado OneToMany (geralmente sua tabela / classe pai), você deve mencionar "mappedBy" (o mapeamento é feito por e na tabela / classe filho), para que o hibernate não crie uma tabela de mapeamento EXTRA no DB (como TableName = parent_child).

Akshay N. Shelke
fonte