Can someone explain mappedBy in JPA and Hibernate?
Asked Answered
M

7

219

I am new to hibernate and need to use one-to-many and many-to-one relations. It is a bi-directional relationship in my objects, so that I can traverse from either direction. mappedBy is the recommended way to go about it, however, I couldn't understand it. Can someone explain:

  • what is the recommended way to use it?
  • what purpose does it solve?

For the sake of my example, here are my classes with annotations:

  • Airline OWNS many AirlineFlights
  • Many AirlineFlights belong to ONE Airline

Airline:

@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;
    }
}

AirlineFlights:

@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);
    }
}

EDIT:

Database schema:

AirlineFlights has the idAirline as ForeignKey and Airline has no idAirlineFlights. This makes, AirlineFlights as the owner/identifying entity ?

Theoretically, I would like airline to be the owner of airlineFlights.

enter image description here

Melodrama answered 2/2, 2012 at 6:46 Comment(1)
Multiple conflicting and thus confusing answers. This question needs new answers.Tangelatangelo
H
166

By specifying the @JoinColumn on both models you don't have a two way relationship. You have two one way relationships, and a very confusing mapping of it at that. You're telling both models that they "own" the IDAIRLINE column. Really only one of them actually should! The 'normal' thing is to take the @JoinColumn off of the @OneToMany side entirely, and instead add mappedBy to the @OneToMany.

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

That tells Hibernate "Go look over on the bean property named 'airline' on the thing I have a collection of to find the configuration."

Hypochlorite answered 2/2, 2012 at 7:32 Comment(8)
I'm a little confused by your description in the end about mappedBy. Does it matter how things are organised in db ? @DB: AirlineFlights has idAirline as the Foreign Key. Airline just have idAirline as Primary key and does not mantain info about AirlineFlights @ DB.Melodrama
Yes, it does matter. The name in mappedBy is telling Hibernate where to find the configuration for the JoinColumn. (On the getAirline() method of AirlineFlight.) The way you mapped it putting the JoinColumn on airline, you are telling Airline that it is responsible for maintaining the values over in the other table. It is possible to tell an Entity it "owns" a column in a different table and is responsible for updating it. It's not something you usually want to do and can cause problems with the order SQL statements are executed.Hypochlorite
Please see the edit. At DB level, airlineFlight table owns idAirline as a foreign key column. Hence, JoinColumn should be put on airlineFlight class/table in the corresponding ManytoOne, since it owns that column ?Melodrama
Yes, I would recommend doing it that way. It is the least complicated option and you don't appear to need anything else.Hypochlorite
"take the @JoinColumn off of the @OneToMany side entirely" you mean of the @ManyToOne side, right?Beaudette
@Beaudette really you don't need it at all for a OneToMany/ManyToOne relationship. The single FK of the "owning" object (Airline ID in this case) in the Many side (AirlineFlight) as handled by mappedBy is all you need. JoinTable is only necessary for ManyToMany relationships that require an entirely separate table to join the two sides.Spina
Specifying the @JoinColumn on both sides is not the reason for creating a two relationships. By default hibernate would create two relationships if you have one side annotated with OneToMany and the other with ManyToOne. If you want to have only one relationship, you'd have to add the mappedBy attribute to the OneToMany side. Please someone correct me if I'm wrong.Tangelatangelo
Are you talking about what hibernate does now or asking if we still remember how it worked 12 years ago? 3.X had many tweaks and optimizations that would infer a lot more meaning from the presence of a JPA (1!) annotation than the spec would strictly define. Most likely doing it either way would have led to the exact same result in 3.X, although if anyone actually wants to dig out and check some 12 year old source code, that exercise is best left to the reader ;)Hypochlorite
C
311

MappedBy signals hibernate that the key for the relationship is on the other side.

This means that although you link 2 tables together, only 1 of those tables has a foreign key constraint to the other one. MappedBy allows you to still link from the table not containing the constraint to the other table.

Chappell answered 2/2, 2012 at 7:31 Comment(5)
can you please clarify a bit more?Griffis
@Kurt Du Bois, why would you use mappedBy rather than define a bi-directional (with foreign_key constraints on each side)?Dracula
Because sometimes it just doesn't make sense to put a key on both sides. Say for example you have a company and a portable. A portable will only belong to one company, but a company will have multiple portables.Chappell
Sorry to the editing guy for rolling back my answer, but there was actually no added value at all in your edit. The last sentence didn't even make sense.Chappell
@KurtDuBois so mapped by come only into picture how to are creating your database,ie either u r using mappedby or not hibernate at java side behaves similar way.Is it?Posting
H
166

By specifying the @JoinColumn on both models you don't have a two way relationship. You have two one way relationships, and a very confusing mapping of it at that. You're telling both models that they "own" the IDAIRLINE column. Really only one of them actually should! The 'normal' thing is to take the @JoinColumn off of the @OneToMany side entirely, and instead add mappedBy to the @OneToMany.

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

That tells Hibernate "Go look over on the bean property named 'airline' on the thing I have a collection of to find the configuration."

Hypochlorite answered 2/2, 2012 at 7:32 Comment(8)
I'm a little confused by your description in the end about mappedBy. Does it matter how things are organised in db ? @DB: AirlineFlights has idAirline as the Foreign Key. Airline just have idAirline as Primary key and does not mantain info about AirlineFlights @ DB.Melodrama
Yes, it does matter. The name in mappedBy is telling Hibernate where to find the configuration for the JoinColumn. (On the getAirline() method of AirlineFlight.) The way you mapped it putting the JoinColumn on airline, you are telling Airline that it is responsible for maintaining the values over in the other table. It is possible to tell an Entity it "owns" a column in a different table and is responsible for updating it. It's not something you usually want to do and can cause problems with the order SQL statements are executed.Hypochlorite
Please see the edit. At DB level, airlineFlight table owns idAirline as a foreign key column. Hence, JoinColumn should be put on airlineFlight class/table in the corresponding ManytoOne, since it owns that column ?Melodrama
Yes, I would recommend doing it that way. It is the least complicated option and you don't appear to need anything else.Hypochlorite
"take the @JoinColumn off of the @OneToMany side entirely" you mean of the @ManyToOne side, right?Beaudette
@Beaudette really you don't need it at all for a OneToMany/ManyToOne relationship. The single FK of the "owning" object (Airline ID in this case) in the Many side (AirlineFlight) as handled by mappedBy is all you need. JoinTable is only necessary for ManyToMany relationships that require an entirely separate table to join the two sides.Spina
Specifying the @JoinColumn on both sides is not the reason for creating a two relationships. By default hibernate would create two relationships if you have one side annotated with OneToMany and the other with ManyToOne. If you want to have only one relationship, you'd have to add the mappedBy attribute to the OneToMany side. Please someone correct me if I'm wrong.Tangelatangelo
Are you talking about what hibernate does now or asking if we still remember how it worked 12 years ago? 3.X had many tweaks and optimizations that would infer a lot more meaning from the presence of a JPA (1!) annotation than the spec would strictly define. Most likely doing it either way would have led to the exact same result in 3.X, although if anyone actually wants to dig out and check some 12 year old source code, that exercise is best left to the reader ;)Hypochlorite
K
41

Table relationship vs. entity relationship

In a relational database system, a one-to-many table relationship looks as follows:

one-to-many table relationship

Note that the relationship is based on the Foreign Key column (e.g., post_id) in the child table.

So, there is a single source of truth when it comes to managing a one-to-many table relationship.

Now, if you take a bidirectional entity relationship that maps on the one-to-many table relationship we saw previously:

Bidirectional One-To-Many entity association

If you take a look at the diagram above, you can see that there are two ways to manage this relationship.

In the Post entity, you have the comments collection:

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

And, in the PostComment, the post association is mapped as follows:

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

Because there are two ways to represent the Foreign Key column, you must define which is the source of truth when it comes to translating the association state change into its equivalent Foreign Key column value modification.

MappedBy

The mappedBy attribute tells that the @ManyToOne side is in charge of managing the Foreign Key column, and the collection is used only to fetch the child entities and to cascade parent entity state changes to children (e.g., removing the parent should also remove the child entities).

Synchronize both sides of a bidirectional association

Now, even if you defined the mappedBy attribute and the child-side @ManyToOne association manages the Foreign Key column, you still need to synchronize both sides of the bidirectional association.

The best way to do that is to add these two utility methods:

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

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

The addComment and removeComment methods ensure that both sides are synchronized. So, if we add a child entity, the child entity needs to point to the parent and the parent entity should have the child contained in the child collection.

Keener answered 27/3, 2020 at 15:40 Comment(4)
can you please elaborate a little more on the utility methods, i am a bit confused as to why do we need synchronization since now after the correct mapping the source of truth is on the many side only i.e., PostComment. isn't it that in the addComment we already have post set in the comment object ?. Also if we just perform delete on comment as an individual entity that would be sufficient right ?Furl
This article explains why you need to have the utility methods.Keener
"the @ManyToOne side is in charge of managing the Foreign Key column," Can you elaborate on what "managing the foreign key" means here?Tangelatangelo
The parent Object reference determines the value of the Foreign Key. If you set the ManyToOne object reference to null, the FK column value will be null. If you set it to reference a parent entity that has an ID value of 100, the FK column value will be 100. That's how the ManyToOne manages the FK column value.Keener
F
25

mappedby speaks for itself, it tells hibernate not to map this field. it's already mapped by this field [name="field"].
field is in the other entity (name of the variable in the class not the table in the database)..

If you don't do that, hibernate will map this two relation as it's not the same relation

so we need to tell hibernate to do the mapping in one side only and co-ordinate between them.

Fallon answered 17/9, 2016 at 15:31 Comment(9)
is mappedBy is optional ? Because without using mappedBy i am getting the same result i.e bidirectional object mappingGlantz
you cannot use on2many and many2one without using mappedBy in one of the side same thing for many2many you have to use mappedBy in one sideFallon
Thanks for pointing out what the attribute value means which is the name of the field in the other table.Rosalindarosalinde
Maybe hibernate doesn't always speak for itself, but when it does, at least it uses punctuationSorensen
For me, it does not speak for itself; conversely, it is very confusing. Just look at the amount of questions regarding what actually is mappedBy and inversedBy. Other ORMs use much more intelligent belongsToMany, hasMany attributes.Themselves
minus 1 for "speaks for itself". It doesn't.Tangelatangelo
@MehdiCharife really! thanks for minus like I really careFallon
Sorry, now it that I have thought about, it makes total sense. I wish there was a way to reverse my minus to a plus.Tangelatangelo
@MehdiCharife no worries my friend one day I carred about this points but now I don't have time anymore to answers question here, believe still receive some comments from people I helped that make my day sometime. all the bestFallon
C
15

mappedby="object of entity of same class created in another class”

Note:-Mapped by can be used only in one class because one table must contain foreign key constraint. if mapped by can be applied on both side then it remove foreign key from both table and without foreign key there is no relation b/w two tables.

Note:- it can be use for following annotations:- 1.@OneTone 2.@OneToMany 3.@ManyToMany

Note---It cannot be use for following annotation :- 1.@ManyToOne

In one to one :- Perform at any side of mapping but perform at only one side . It will remove the extra column of foreign key constraint on the table on which class it is applied.

For eg . If we apply mapped by in Employee class on employee object then foreign key from Employee table will be removed.

Confer answered 18/1, 2017 at 6:51 Comment(0)
S
3

The mappedBy attribute characterizes a bidirectional association and must be set on the parent-side. In other words, for a bidirectional @OneToMany association, set mappedBy to @OneToMany on the parent-side and add @ManyToOne on the child-side referenced by mappedBy . Via mappedBy, the bidirectional @OneToMany association signals that it mirrors the @ManyToOne child-side mapping.

Steck answered 16/4, 2021 at 6:50 Comment(1)
Why are you italicizing your answer? Are you quoting the docs?Tangelatangelo
M
0

You started with ManyToOne mapping , then you put OneToMany mapping as well for BiDirectional way. Then at OneToMany side (usually your parent table/class), you have to mention "mappedBy" (mapping is done by and in child table/class), so hibernate will not create EXTRA mapping table in DB (like TableName = parent_child).

Minaminabe answered 23/9, 2017 at 10:59 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.