¿Alguien puede explicar mappedBy en JPA e Hibernate?
Soy nuevo en hibernación y necesito usar relaciones uno a muchos y muchos a uno. Es una relación bidireccional en mis objetos, de modo que puedo atravesar desde cualquier dirección. mappedBy
es la forma recomendada de hacerlo, sin embargo, no pude entenderlo. ¿Alguien puede explicar:
- ¿Cuál es la forma recomendada de usarlo?
- ¿Qué propósito resuelve?
Por el bien de mi ejemplo, aquí están mis clases con anotaciones:
Airline
DUEÑO DE MUCHOSAirlineFlights
- Muchos
AirlineFlights
pertenecen a UNOAirline
Aerolínea :
@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;
}
}
Vuelos de aerolíneas:
@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 de base de datos:
AirlineFlights tiene idAirline como ForeignKey y Airline no tiene idAirlineFlights. ¿Esto convierte a AirlineFlights en el propietario/entidad identificadora?
En teoría, me gustaría que la aerolínea fuera propietaria de AirlinesFlights.
MappedBy indica en hibernación que la clave de la relación está del otro lado.
Esto significa que aunque vincule 2 tablas, solo 1 de esas tablas tiene una restricción de clave externa para la otra. MappedBy le permite seguir vinculando desde la tabla que no contiene la restricción a la otra tabla.
Al especificar @JoinColumn
en ambos modelos, no tiene una relación bidireccional. Tienes dos relaciones unidireccionales y, además, un mapeo muy confuso. Le estás diciendo a ambos modelos que "son dueños" de la columna IDAIRLINE. ¡Realmente sólo uno de ellos debería hacerlo! Lo "normal" es quitar @JoinColumn
el @OneToMany
lado por completo y, en su lugar, agregar mappedBy al archivo @OneToMany
.
@OneToMany(cascade = CascadeType.ALL, mappedBy="airline")
public Set<AirlineFlight> getAirlineFlights() {
return airlineFlights;
}
Eso le dice a Hibernate "Vaya a revisar la propiedad del bean llamada 'aerolínea' en lo que tengo una colección para encontrar la configuración".
Relación de tabla versus relación de entidad
En un sistema de base de datos relacional, una one-to-many
relación de tabla tiene el siguiente aspecto:
Tenga en cuenta que la relación se basa en la columna de clave externa (por ejemplo, post_id
) en la tabla secundaria.
Por lo tanto, existe una única fuente de verdad cuando se trata de gestionar una one-to-many
relación en la mesa.
Ahora, si toma una relación de entidad bidireccional que se asigna a la one-to-many
relación de tabla que vimos anteriormente:
Si observa el diagrama anterior, podrá ver que hay dos formas de gestionar esta relación.
En la Post
entidad tienes la comments
colección:
@OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PostComment> comments = new ArrayList<>();
Y, en PostComment
, la post
asociación se asigna de la siguiente manera:
@ManyToOne(
fetch = FetchType.LAZY
)
@JoinColumn(name = "post_id")
private Post post;
Debido a que hay dos formas de representar la columna de clave externa, debe definir cuál es la fuente de verdad cuando se trata de traducir el cambio de estado de la asociación en su modificación del valor de la columna de clave externa equivalente.
Mapeado por
El mappedBy
atributo indica que el @ManyToOne
lado está a cargo de administrar la columna de clave externa, y la colección se usa solo para recuperar las entidades secundarias y para transmitir en cascada los cambios de estado de la entidad principal a las secundarias (por ejemplo, eliminar la entidad principal también debería eliminar las entidades secundarias).
Sincronizar ambos lados de una asociación bidireccional
Ahora, incluso si definió el mappedBy
atributo y la asociación del lado secundario @ManyToOne
administra la columna de clave externa, aún necesita sincronizar ambos lados de la asociación bidireccional.
La mejor manera de hacerlo es agregar estos dos métodos de utilidad:
public void addComment(PostComment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(PostComment comment) {
comments.remove(comment);
comment.setPost(null);
}
Los métodos addComment
y removeComment
garantizan que ambos lados estén sincronizados. Entonces, si agregamos una entidad secundaria, la entidad secundaria debe apuntar a la entidad principal y la entidad principal debe tener al hijo contenido en la colección secundaria.