¿Cuál es la diferencia entre @JoinColumn y mappedBy cuando se usa una asociación JPA @OneToMany

516

Cuál es la diferencia entre:

@Entity
public class Company {

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

y

@Entity
public class Company {

    @OneToMany(cascade = CascadeType.ALL , fetch = FetchType.LAZY, mappedBy = "companyIdRef")
    private List<Branch> branches;
    ...
}
Mykhaylo Adamovych
fuente
1
También vea ¿Cuál es el lado propietario en una pregunta de mapeo ORM para una muy buena explicación de los problemas involucrados.
dirkt

Respuestas:

545

La anotación @JoinColumnindica que esta entidad es la propietaria de la relación (es decir: la tabla correspondiente tiene una columna con una clave foránea para la tabla referenciada), mientras que el atributo mappedByindica que la entidad en este lado es el inverso de la relación, y el propietario reside en la "otra" entidad. Esto también significa que puede acceder a la otra tabla desde la clase que ha anotado con "mappedBy" (relación totalmente bidireccional).

En particular, para el código en la pregunta, las anotaciones correctas se verían así:

@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
fuente
3
en ambos casos, Branch tiene un campo con ID de empresa.
Mykhaylo Adamovych
3
La tabla de la empresa no tiene una columna con una clave externa para la tabla referenciada: Branch ha hecho referencia a la empresa ... ¿por qué dice "la tabla correspondiente tiene una columna con una clave externa para la tabla referenciada"? ¿Podría explicar algunos más por favor?
Mykhaylo Adamovych
13
@ MykhayloAdamovych Actualicé mi respuesta con un código de muestra. Tenga en cuenta que se trata de un error utilizar @JoinColumnenCompany
Óscar López
10
@MykhayloAdamovych: No, en realidad no está del todo bien. Si Branchno tiene una propiedad que haga referencia Company, pero la tabla subyacente tiene una columna que sí, puede usarla @JoinTablepara asignarla. Esta es una situación inusual, porque normalmente mapearía la columna en el objeto que corresponde a su tabla, pero puede suceder y es perfectamente legítimo.
Tom Anderson
44
Esta es otra razón para no gustar los ORM. La documentación es a menudo demasiado dudosa, y en mis libros, esto es serpenteante en el territorio mágico demasiado. He estado luchando con este problema y cuando se sigue palabra por palabra un a @OneToOne, las filas secundarias se actualizan con un nullen su columna FKey que hace referencia al padre.
Ashesh
225

@JoinColumnpodría usarse en ambos lados de la relación. La pregunta era sobre el uso @JoinColumnen el @OneToManylado (caso raro). Y el punto aquí está en la duplicación de información física (nombre de columna) junto con una consulta SQL no optimizada que producirá algunas UPDATEdeclaraciones adicionales .

Según la documentación :

Dado que muchos a uno son (casi) siempre el lado propietario de una relación bidireccional en la especificación JPA, la asociación uno a muchos está 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() {
    ...
} 

Trooptiene una relación bidireccional de uno a muchos a Soldiertravés de la propiedad de la tropa. No es necesario (no debe) definir ningún mapeo físico en el mappedBylateral.

Para asignar un ser bidireccional para muchos, con el lado de uno-a-muchos como el lado propietario , usted tiene que quitar el mappedByelemento y establecer el muchos a uno @JoinColumncomo insertabley updatableen false. Esta solución no está optimizada y producirá algunas UPDATEdeclaraciones adicionales .

@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
fuente
1
No puedo entender cómo Troop puede ser propietario en su segundo fragmento, Soldier sigue siendo el propietario, ya que contiene claves externas que hacen referencia a Troop. (Estoy usando mysql, verifiqué con tu enfoque).
Akhilesh
10
En su ejemplo, la anotación se mappedBy="troop"refiere a qué campo?
Fractaliste
55
@Fractaliste la anotación se mappedBy="troop"refiere a la tropa de propiedad en la clase Soldado. En el código anterior, la propiedad no es visible porque aquí Mykhaylo la omitió, pero puede deducir su existencia mediante el getter getTroop (). Comprueba la respuesta de Óscar López , está muy claro y entenderás el punto.
nicolimo86
1
Este ejemplo es abuso de la especificación JPA 2. Si el objetivo del autor es crear una relación bidireccional, debe usar mappedBy en el lado primario y JoinColumn (si es necesario) en el lado secundario. Con el enfoque presentado aquí, obtenemos 2 relaciones unidireccionales: OneToMany y ManyToOne que son independientes pero solo por suerte (más por mal uso) esas 2 relaciones se definen usando la misma clave externa
aurelije
1
Si está utilizando JPA 2.x, mi respuesta a continuación es un poco más limpia. Aunque sugiero probar ambas rutas y ver qué hace Hibernate cuando genera las tablas. Si está en un nuevo proyecto, elija la generación que crea que satisfaga sus necesidades. Si está en una base de datos heredada y no desea cambiar la estructura, elija la que coincida con su esquema.
Snekse
65

Como esta es una pregunta muy común, escribí este artículo , en el que se basa esta respuesta.

Asociación unidireccional de uno a muchos

Como expliqué en este artículo , si usa la @OneToManyanotación con @JoinColumn, entonces tiene una asociación unidireccional, como la que existe entre la Postentidad principal y el elemento secundario PostCommenten el siguiente diagrama:

Asociación unidireccional de uno a muchos

Cuando se usa una asociación unidireccional de uno a muchos, solo el lado padre mapea la asociación.

En este ejemplo, solo la Postentidad definirá una @OneToManyasociación con la PostCommententidad secundaria :

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

Asociación bidireccional uno a muchos

Si usa el @OneToManycon el mappedByconjunto de atributos, tiene una asociación bidireccional. En nuestro caso, tanto la Postentidad tiene una colección de PostCommententidades secundarias como la PostCommententidad secundaria tiene una referencia a la Postentidad principal , como se ilustra en el siguiente diagrama:

Asociación bidireccional uno a muchos

En la PostCommententidad, la postpropiedad de la entidad se asigna de la siguiente manera:

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

La razón por la que establece explícitamente la fetchatributo a FetchType.LAZYse debe a que, por defecto, todos @ManyToOney @OneToOneasociaciones se recuperan con impaciencia, que puede causar N + 1 temas de consulta. Para obtener más detalles sobre este tema, consulte este artículo .

En la Postentidad, la commentsasociación se asigna de la siguiente manera:

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

El mappedByatributo de la @OneToManyanotación hace referencia a la postpropiedad en la PostCommententidad secundaria y, de esta manera, Hibernate sabe que la asociación bidireccional está controlada por el @ManyToOnelado, que se encarga de administrar el valor de la columna Clave externa en la que se basa esta relación de tabla.

Para una asociación bidireccional, también debe tener dos métodos de utilidad, como addChildy removeChild:

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

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

Estos dos métodos aseguran que ambos lados de la asociación bidireccional no estén sincronizados. Sin sincronizar ambos extremos, Hibernate no garantiza que los cambios de estado de asociación se propaguen a la base de datos.

Para obtener más detalles sobre el mejor wat para sincronizar asociaciones bidireccionales con JPA e Hibernate, consulte este artículo .

¿Cuál elegir?

La asociación unidireccional @OneToManyno funciona muy bien , por lo que debe evitarla.

Es mejor usar el bidireccional, @OneToManyque es más eficiente .

Vlad Mihalcea
fuente
32

La anotación mappedBy idealmente siempre debe usarse en el lado principal (clase de empresa) de la relación bidireccional, en este caso debe estar en la clase de empresa que apunta a la variable miembro 'empresa' de la clase secundaria (clase de sucursal)

La anotación @JoinColumn se usa para especificar una columna asignada para unirse a una asociación de entidad, esta anotación se puede usar en cualquier clase (padre o hijo), pero idealmente se debe usar solo en un lado (en la clase padre o en la clase hija no en ambos) aquí, en este caso, lo usé en el lado secundario (clase Branch) de la relación bidireccional que indica la clave foránea en la clase Branch.

A continuación se muestra el ejemplo de trabajo:

clase matriz, 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;
    }


}

clase infantil, rama

@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
fuente
20

Solo me gustaría agregar que @JoinColumnno siempre tiene que estar relacionado con la ubicación de la información física, como sugiere esta respuesta. Se pueden combinar @JoinColumncon @OneToManyincluso si la tabla primaria no tiene datos de la tabla apuntando a la tabla secundaria.

Cómo definir la relación unidireccional OneToMany en JPA

Unidireccional OneToMany, No Inverse ManyToOne, No Join Table

Sin JPA 2.x+embargo, parece que solo está disponible en . Es útil para situaciones en las que desea que la clase secundaria solo contenga el ID del padre, no una referencia completa.

Snekse
fuente
tiene razón, el soporte para OneToMany unidireccional sin tabla de unión se presenta en JPA2
aurelije
17

No estoy de acuerdo con la respuesta aceptada aquí por Óscar López. ¡Esa respuesta es inexacta!

NO es lo @JoinColumnque indica que esta entidad es la propietaria de la relación. En cambio, es la @ManyToOneanotación la que hace esto (en su ejemplo).

Las anotaciones de relación como @ManyToOne, @OneToManyy le @ManyToManydicen a JPA / Hibernate que cree un mapeo. Por defecto, esto se realiza a través de una tabla de unión separada.


@JoinColumn

El propósito de @JoinColumnes crear una columna de unión si aún no existe una. Si lo hace, esta anotación se puede usar para nombrar la columna de unión.


MappedBy

El propósito del MappedByparámetro es instruir a JPA: NO cree otra tabla de unión ya que la relación ya está siendo asignada por la entidad opuesta de esta relación.



Recuerde: MappedByes una propiedad de las anotaciones de relación cuyo propósito es generar un mecanismo para relacionar dos entidades que por defecto lo hacen al crear una tabla de unión. MappedBydetiene ese proceso en una dirección.

Se MappedBydice que la entidad que no utiliza es la propietaria de la relación porque la mecánica de la asignación se dicta dentro de su clase mediante el uso de una de las tres anotaciones de asignación en el campo de clave externa. Esto no solo especifica la naturaleza de la asignación, sino que también indica la creación de una tabla de unión. Además, la opción de suprimir la tabla de unión también existe aplicando la anotación @JoinColumn sobre la clave externa que la mantiene dentro de la tabla de la entidad propietaria.

En resumen: @JoinColumncrea una nueva columna de unión o renombra una existente; mientras que el MappedByparámetro funciona en colaboración con las anotaciones de relación de la otra clase (secundaria) para crear un mapeo a través de una tabla de unión o creando una columna de clave externa en la tabla asociada de la entidad propietaria.

Para ilustrar cómo MapppedByfunciona, considere el siguiente código. Si MappedByse eliminara el parámetro, ¡Hibernate crearía DOS tablas de unión! ¿Por qué? Debido a que existe una simetría en las relaciones de muchos a muchos, Hibernate no tiene fundamentos para seleccionar una dirección sobre la otra.

Por lo tanto MappedBy, solemos decirle a Hibernate que hemos elegido la otra entidad para dictar el mapeo de la relación entre las dos entidades.

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

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

Agregar @JoinColumn (name = "driverID") en la clase de propietario (ver más abajo), evitará la creación de una tabla de unión y, en su lugar, creará una columna de clave externa driverID en la tabla Cars para construir una asignación:

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

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

JPA es una API en capas, los diferentes niveles tienen sus propias anotaciones. El nivel más alto es el (1) nivel de entidad que describe las clases persistentes, entonces tiene el (2) nivel de base de datos relacional que supone que las entidades se asignan a una base de datos relacional y (3) el modelo de Java.

Nivel 1 anotaciones: @Entity, @Id, @OneToOne, @OneToMany, @ManyToOne, @ManyToMany. Puede introducir persistencia en su aplicación utilizando solo estas anotaciones de alto nivel. Pero luego debe crear su base de datos de acuerdo con los supuestos que hace JPA. Estas anotaciones especifican el modelo de entidad / relación.

Nivel 2 anotaciones: @Table, @Column, @JoinColumn, ... influir en la asignación de entidades / propiedades a las mesas de bases de datos relacionales / columnas si no está satisfecho con los valores predeterminados de la APP o si necesita asignar a una base de datos existente. Estas anotaciones pueden verse como anotaciones de implementación, especifican cómo se debe hacer la asignación.

En mi opinión, es mejor atenerse lo más posible a las anotaciones de alto nivel y luego introducir las anotaciones de nivel inferior según sea necesario.

Para responder las preguntas: el @OneToMany/ mappedByes más agradable porque solo usa las anotaciones del dominio de la entidad. El @oneToMany/ @JoinColumntambién está bien, pero utiliza una anotación de implementación donde esto no es estrictamente necesario.

Bruno Ranschaert
fuente
1

Déjame hacerlo simple.
Puede usar @JoinColumn en ambos lados, independientemente de la asignación.

Dividamos esto en tres casos.
1) Mapeo unidireccional de sucursal a empresa.
2) Mapeo bidireccional de empresa a sucursal.
3) Solo mapeo unidireccional de empresa a sucursal.

Por lo tanto, cualquier caso de uso se incluirá en estas tres categorías. Déjame explicarte cómo usar @JoinColumn y mappedBy .
1) Mapeo unidireccional de sucursal a empresa.
Use JoinColumn en la tabla Branch.
2) Mapeo bidireccional de empresa a sucursal.
Use mappedBy en la tabla de la compañía como se describe en la respuesta de @Mykhaylo Adamovych.
3) Mapeo unidireccional de empresa a sucursal.
Solo use @JoinColumn en la tabla Compañía.

@Entity
public class Company {

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

Esto dice que en función de la asignación de clave externa "courseId" en la tabla de sucursales, obtener una lista de todas las sucursales. NOTA: en este caso, no puede obtener una empresa de una sucursal, solo existe una asignación unidireccional de una empresa a otra.

Vinjamuri Satya Krishna
fuente