Qual é a diferença entre @JoinColumn e mappedBy ao usar uma associação JPA @OneToMany

516

Qual é a diferença entre:

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
    @JoinColumn(name = "companyIdRef", referencedColumnName = "companyId")
    private List<Branch> branches;
    ...
}

e

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}
Mykhaylo Adamovych
fonte
1
Consulte também Qual é o lado proprietário em uma pergunta de mapeamento ORM para uma explicação realmente boa dos problemas envolvidos.
dirkt

Respostas:

545

A anotação @JoinColumnindica que esta entidade é o proprietário do relacionamento (ou seja: a tabela correspondente possui uma coluna com uma chave estrangeira para a tabela referenciada), enquanto o atributo mappedByindica que a entidade deste lado é o inverso do relacionamento, e o proprietário reside na "outra" entidade. Isso também significa que você pode acessar a outra tabela da classe que você anotou com "mappedBy" (relacionamento totalmente bidirecional).

Em particular, para o código da pergunta, as anotações corretas teriam a seguinte aparência:

@Entity
public class Company {
    @OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
    private List<Branch> branches;
}

@Entity
public class Branch {
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "companyId")
    private Company company;
}
Óscar López
fonte
3
nos dois casos, a filial tem um campo com o ID da empresa.
Mykhaylo Adamovych
3
A tabela da empresa não possui uma coluna com uma chave estrangeira na tabela referenciada - A filial se refere à empresa .. por que você está dizendo "a tabela correspondente possui uma coluna com uma chave estrangeira na tabela referenciada"? Você poderia explicar mais alguns pls.
Mykhaylo Adamovych
13
@MykhayloAdamovych Atualizei minha resposta com o código de exemplo. Observe que é um erro usá-lo @JoinColumnemCompany
Óscar López
10
@MykhayloAdamovych: Não, isso não está certo. Se Branchnão tiver uma propriedade que faça referência Company, mas a tabela subjacente tiver uma coluna que tenha, você poderá usar @JoinTablepara mapeá-la. Essa é uma situação incomum, porque você normalmente mapeia a coluna no objeto que corresponde à sua tabela, mas pode acontecer e é perfeitamente legítima.
Tom Anderson
4
Esse é outro motivo para não gostar dos ORMs. A documentação é muitas vezes desonesta e, nos meus livros, isso é sinuoso no território mágico demais. Estou enfrentando problemas com esse problema e, quando seguidos palavra por palavra por a @OneToOne, as linhas filho são atualizadas com um nullna coluna FKey que faz referência ao pai.
Ashesh
225

@JoinColumnpoderia ser usado nos dois lados do relacionamento. A pergunta era sobre o uso @JoinColumnao @OneToManylado (caso raro). E o ponto aqui é a duplicação de informações físicas (nome da coluna) junto com a consulta SQL não otimizada que produzirá algumas UPDATEinstruções adicionais .

De acordo com a documentação :

Como muitos para um são (quase) sempre o lado proprietário de um relacionamento bidirecional na especificação da JPA, a associação de um para muitos é anotada por@OneToMany(mappedBy=...)

@Entity
public class Troop {
    @OneToMany(mappedBy="troop")
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk")
    public Troop getTroop() {
    ...
} 

Trooptem um relacionamento bidirecional com muitos Soldieratravés da propriedade da tropa. Você não precisa (não deve) definir nenhum mapeamento físico no mappedBylado.

Para mapear um bidirecional para muitos, com o lado um para muitos como o lado proprietário , você deve remover o mappedByelemento e definir os muitos para um @JoinColumncomo insertablee updatablefalse. Esta solução não é otimizada e produzirá algumas UPDATEinstruções adicionais .

@Entity
public class Troop {
    @OneToMany
    @JoinColumn(name="troop_fk") //we need to duplicate the physical information
    public Set<Soldier> getSoldiers() {
    ...
}

@Entity
public class Soldier {
    @ManyToOne
    @JoinColumn(name="troop_fk", insertable=false, updatable=false)
    public Troop getTroop() {
    ...
}
Mykhaylo Adamovych
fonte
1
Não consigo descobrir como o Troop pode ser o proprietário no seu segundo trecho; o Soldier ainda é o proprietário, pois contém uma referência de chave estrangeira ao Troop. (Estou usando o mysql, verifiquei com a sua abordagem).
Akhilesh #
10
No seu exemplo, a anotação se mappedBy="troop"refere a qual campo?
Fractaliste
5
@Fractaliste a anotação mappedBy="troop"se refere à tropa de propriedades na classe Soldier. No código acima, a propriedade não é visível porque aqui Mykhaylo a omitiu, mas você pode deduzir sua existência pelo getter getTroop (). Verifique a resposta de Óscar López , é muito claro e você entenderá.
Nicolimo86
1
Este exemplo é abuso da especificação JPA 2. Se o objetivo do autor for criar uma relação bidirecional, ele deverá usar mappedBy no lado pai e JoinColumn (se necessário) no lado filho. Com abordagem apresentada aqui estamos recebendo 2 relações unidirecionais: OneToMany e ManyToOne que são independentes, mas apenas por sorte (mais pelo mau uso) esses 2 relações são definidas usando mesma chave estrangeira
aurelije
1
Se você estiver usando o JPA 2.x, minha resposta abaixo é um pouco mais limpa. Embora eu sugira tentar as duas rotas e ver o que o Hibernate faz quando gera as tabelas. Se você estiver em um novo projeto, escolha a geração que julgar adequada às suas necessidades. Se você estiver em um banco de dados legado e não quiser alterar a estrutura, escolha o que corresponder ao seu esquema.
precisa saber é o seguinte
65

Como essa é uma pergunta muito comum, escrevi este artigo , no qual essa resposta se baseia.

Associação unidirecional um para muitos

Como expliquei neste artigo , se você usar a @OneToManyanotação com @JoinColumn, terá uma associação unidirecional, como a que existe entre a Postentidade pai e o filho PostCommentno diagrama a seguir:

Associação unidirecional um para muitos

Ao usar uma associação unidirecional um para muitos, apenas o lado dos pais mapeia a associação.

Neste exemplo, apenas a Postentidade definirá uma @OneToManyassociação à PostCommententidade filha :

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

Associação bidirecional um para muitos

Se você usar o @OneToManycom o mappedByconjunto de atributos, você terá uma associação bidirecional. No nosso caso, a Postentidade possui uma coleção de PostCommententidades filhas e a PostCommententidade filha possui uma referência à Postentidade pai , conforme ilustrado no diagrama a seguir:

Associação bidirecional um para muitos

Na PostCommententidade, a postpropriedade da entidade é mapeada da seguinte maneira:

@ManyToOne(fetch = FetchType.LAZY)
private Post post;

A razão por que definir explicitamente o fetchatributo para FetchType.LAZYé porque, por padrão, todos @ManyToOnee @OneToOneassociações são buscadas avidamente, o que pode causar N + 1 questões da consulta. Para mais detalhes sobre este tópico, consulte este artigo .

Na Postentidade, a commentsassociação é mapeada da seguinte maneira:

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

O mappedByatributo da @OneToManyanotação faz referência à postpropriedade na PostCommententidade filha e, dessa forma, o Hibernate sabe que a associação bidirecional é controlada pelo @ManyToOnelado, responsável por gerenciar o valor da coluna Chave Externa na qual o relacionamento da tabela se baseia.

Para uma associação bidirecional, você também precisa ter dois métodos utilitários, como addChilde removeChild:

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

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

Esses dois métodos garantem que os dois lados da associação bidirecional não sejam sincronizados. Sem sincronizar as duas extremidades, o Hibernate não garante que as mudanças no estado da associação sejam propagadas para o banco de dados.

Para obter mais detalhes sobre o melhor modo de sincronizar associações bidirecionais com JPA e Hibernate, consulte este artigo .

Qual escolher?

A associação unidirecional @OneToManynão funciona muito bem ; portanto, você deve evitá-la.

É melhor usar o bidirecional @OneToManyque é mais eficiente .

Vlad Mihalcea
fonte
32

A anotação mappedBy idealmente sempre deve ser usada no lado pai (classe Company) do relacionamento bidirecional, nesse caso, deve estar na classe Company apontando para a variável de membro 'company' da classe Child (classe Branch)

A anotação @JoinColumn é usada para especificar uma coluna mapeada para ingressar em uma associação de entidade. Essa anotação pode ser usada em qualquer classe (pai ou filho), mas idealmente deve ser usada apenas em um lado (na classe pai ou na classe filho não em ambos) aqui, neste caso, usei-o no lado filho (classe Branch) da relação bidirecional, indicando a chave estrangeira na classe Branch.

abaixo está o exemplo de trabalho:

classe pai, empresa

@Entity
public class Company {


    private int companyId;
    private String companyName;
    private List<Branch> branches;

    @Id
    @GeneratedValue
    @Column(name="COMPANY_ID")
    public int getCompanyId() {
        return companyId;
    }

    public void setCompanyId(int companyId) {
        this.companyId = companyId;
    }

    @Column(name="COMPANY_NAME")
    public String getCompanyName() {
        return companyName;
    }

    public void setCompanyName(String companyName) {
        this.companyName = companyName;
    }

    @OneToMany(fetch=FetchType.LAZY,cascade=CascadeType.ALL,mappedBy="company")
    public List<Branch> getBranches() {
        return branches;
    }

    public void setBranches(List<Branch> branches) {
        this.branches = branches;
    }


}

classe infantil, Filial

@Entity
public class Branch {

    private int branchId;
    private String branchName;
    private Company company;

    @Id
    @GeneratedValue
    @Column(name="BRANCH_ID")
    public int getBranchId() {
        return branchId;
    }

    public void setBranchId(int branchId) {
        this.branchId = branchId;
    }

    @Column(name="BRANCH_NAME")
    public String getBranchName() {
        return branchName;
    }

    public void setBranchName(String branchName) {
        this.branchName = branchName;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="COMPANY_ID")
    public Company getCompany() {
        return company;
    }

    public void setCompany(Company company) {
        this.company = company;
    }


}
Coral
fonte
20

Gostaria apenas de acrescentar que @JoinColumnnem sempre é necessário estar relacionado ao local da informação física, como sugere esta resposta. Você pode combinar @JoinColumncom @OneToManymesmo que a tabela pai não possua dados da tabela apontando para a tabela filha.

Como definir o relacionamento OneToMany unidirecional no JPA

OneToMany unidirecional, sem ManyToOne inverso, sem tabela de associação

Parece estar disponível apenas no JPA 2.x+entanto. É útil para situações em que você deseja que a classe filho contenha apenas o ID do pai, não uma referência completa.

Snekse
fonte
você está certo, o suporte ao OneToMany unidirecional sem tabela de junção é introduzido no JPA2
aurelije
17

Discordo da resposta aceita aqui por Óscar López. Essa resposta é imprecisa!

NÃO é o @JoinColumnque indica que esta entidade é o proprietário do relacionamento. Em vez disso, é o@ManyToOne anotação que faz isso (no exemplo dele).

As anotações de relacionamento, como @ManyToOne, @OneToManye @ManyToManydizem ao JPA / Hibernate para criar um mapeamento. Por padrão, isso é feito através de uma Tabela de associação separada.


@JoinColumn

O objetivo de @JoinColumné criar uma coluna de junção, se uma ainda não existir. Se houver, essa anotação poderá ser usada para nomear a coluna de junção.


MappedBy

O objetivo do MappedByparâmetro é instruir a JPA: NÃO crie outra tabela de junção, pois o relacionamento já está sendo mapeado pela entidade oposta desse relacionamento.



Lembre-se: MappedByé uma propriedade das anotações de relacionamento cujo objetivo é gerar um mecanismo para relacionar duas entidades que, por padrão, o fazem criando uma tabela de junção. MappedByinterrompe esse processo em uma direção.

MappedByDiz-se que a entidade que não está usando é a proprietária do relacionamento porque a mecânica do mapeamento é ditada dentro de sua classe através do uso de uma das três anotações de mapeamento no campo de chave estrangeira. Isso não apenas especifica a natureza do mapeamento, mas também instrui a criação de uma tabela de junção. Além disso, a opção de suprimir a tabela de junção também existe aplicando a anotação @JoinColumn sobre a chave estrangeira, que a mantém dentro da tabela da entidade proprietária.

Em resumo: @JoinColumncria uma nova coluna de junção ou renomeia uma existente; enquanto oMappedBy parâmetro trabalha em colaboração com as anotações de relacionamento da outra classe (filha) para criar um mapeamento por meio de uma tabela de junção ou criando uma coluna de chave estrangeira na tabela associada da entidade proprietária.

Para ilustrar como MapppedByfunciona, considere o código abaixo. Se o MappedByparâmetro fosse excluído, o Hibernate criaria DUAS tabelas de junção! Por quê? Como existe simetria nos relacionamentos muitos para muitos, o Hibernate não tem lógica para selecionar uma direção em relação à outra.

Portanto, usamos MappedBypara informar ao Hibernate que escolhemos a outra entidade para ditar o mapeamento do relacionamento entre as duas entidades.

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    private List<Drivers> drivers;
}

Adicionar @JoinColumn (name = "driverID") na classe do proprietário (veja abaixo) impedirá a criação de uma tabela de junção e, em vez disso, criará uma coluna de chave estrangeira driverID na tabela Cars para construir um mapeamento:

@Entity
public class Driver {
    @ManyToMany(mappedBy = "drivers")
    private List<Cars> cars;
}

@Entity
public class Cars {
    @ManyToMany
    @JoinColumn(name = "driverID")
    private List<Drivers> drivers;
}
IqbalHamid
fonte
1

JPA é uma API em camadas, os diferentes níveis têm suas próprias anotações. O nível mais alto é o (1) nível de entidade que descreve as classes persistentes; então, você tem o (2) nível de banco de dados relacional que assume que as entidades estão mapeadas para um banco de dados relacional e (3) o modelo java.

Nível 1 anotações: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne,@ManyToMany . Você pode introduzir persistência no seu aplicativo usando apenas essas anotações de alto nível. Mas então você deve criar seu banco de dados de acordo com as suposições que o JPA faz. Essas anotações especificam o modelo de entidade / relacionamento.

Nível 2 anotações: @Table, @Column, @JoinColumn, ... Influência do mapeamento de entidades / propriedades para as tabelas de banco de dados relacionais / colunas se você não estiver satisfeito com os padrões da APP ou se você precisa mapear para um banco de dados existente. Essas anotações podem ser vistas como anotações de implementação, especificando como o mapeamento deve ser feito.

Na minha opinião, é melhor manter o máximo possível as anotações de alto nível e, em seguida, introduzir as anotações de nível inferior, conforme necessário.

Para responder às perguntas: o @OneToMany/ mappedByé melhor porque usa apenas as anotações do domínio da entidade. O @oneToMany/ @JoinColumntambém é bom, mas usa uma anotação de implementação onde isso não é estritamente necessário.

Bruno Ranschaert
fonte
1

Deixe-me simplificar.
Você pode usar @JoinColumn em ambos os lados, independentemente do mapeamento.

Vamos dividir isso em três casos.
1) Mapeamento unidirecional da Filial para a Empresa.
2) Mapeamento bidirecional da empresa para a filial.
3) Somente mapeamento unidirecional da empresa para a filial.

Portanto, qualquer caso de uso se enquadra nessas três categorias. Então, deixe-me explicar como usar @JoinColumn e mappedBy .
1) Mapeamento unidirecional da Filial para a Empresa.
Use JoinColumn na tabela Filial.
2) Mapeamento bidirecional da empresa para a filial.
Use a tabela mappedBy in Company, conforme descrito pela resposta de @Mykhaylo Adamovych.
3) Mapeamento unidirecional da empresa para a filial.
Basta usar @JoinColumn na tabela Empresa.

@Entity
public class Company {

@OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY)
@JoinColumn(name="courseId")
private List<Branch> branches;
...
}

Isso diz que, com base no mapeamento da chave estrangeira "courseId" na tabela branches, me dê uma lista de todos os branches. NOTA: neste caso, não é possível buscar a empresa da filial, apenas o mapeamento unidirecional existe de empresa para filial.

Vinjamuri Satya Krishna
fonte