有人能解释一下JPA和Hibernate中的mappedBy吗?

216
我是hibernate的新手,需要使用一对多和多对一关系。在我的对象中有双向关系,因此我可以从任意一个方向遍历。mappedBy 是推荐的方法,但我无法理解它。请问:
  • 什么是建议的使用方式?
  • 它解决了什么目的?
为了举例说明,这里是我的带注释的类:
  • Airline 拥有多个 AirlineFlights
  • 许多 AirlineFlights 属于 一个 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);
    }
}

编辑:

数据库模式:

AirlineFlights有idAirline作为ForeignKey,而Airline没有idAirlineFlights。这使得AirlineFlights成为所有者/识别实体?

理论上,我希望Airline是airlineFlights的所有者。

7个回答

310

MappedBy表示关系的键在另一侧,这意味着虽然将两个表链接在一起,但只有其中1个表对另一个表具有外键约束。MappedBy使您仍然可以从不包含约束的表链接到其他表。


6
您能否再详细解释一下? - Alexander Suraphel
1
@Kurt Du Bois,为什么你要使用mappedBy而不是定义一个双向关系(在每一侧都有外键约束)? - Kevin Meredith
8
因为有时在两侧都放置钥匙就没有意义。例如,假设您拥有一家公司和一个便携式设备。便携式设备只属于一个公司,但是一个公司可能会拥有多个便携式设备。 - Kurt Du Bois
对于编辑人员的回滚,我感到抱歉,但实际上你的编辑并没有增加任何价值。最后一句话甚至没有意义。 - Kurt Du Bois
@KurtDuBois,只有在创建数据库时使用了mappedby,或者没有使用Hibernate,Java端的行为才会类似。 - user4768611

165

如果在两个模型上都指定了@JoinColumn,那么你并没有建立双向关系,而是建立了两个单向关系,映射方式非常混乱。你告诉这两个模型它们都"拥有"IDAIRLINE列,但实际上只有一个模型应该拥有!通常的做法是完全去掉@OneToMany侧的@JoinColumn,而是在@OneToMany上添加mappedBy。

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

这告诉了 Hibernate:“去查找我所拥有的集合中名为‘airline’的 bean 属性以找到配置。”


2
我对你最后关于mappedBy的描述有点困惑。数据库中的组织方式是否重要?@DB:AirlineFlights具有idAirline作为外键。Airline只有idAirline作为主键,并且不维护有关AirlineFlights @ DB的信息。 - brainydexter
10
是的,这很重要。mappedBy中的名称告诉Hibernate在哪里查找JoinColumn的配置(在AirlineFlight类的getAirline()方法上)。您将JoinColumn映射到airline上的方式告诉Airline它负责维护另一个表中的值。可以告诉实体它“拥有”不同表中的列并负责更新它,但这通常是不必要的,并且可能会导致执行SQL语句的顺序出现问题。 - Affe
请查看编辑。在数据库层面,airlineFlight表拥有idAirline作为外键列。因此,在相应的ManyToOne中,JoinColumn应该放在airlineFlight类/表上,因为它拥有该列。 - brainydexter
是的,我建议用那种方法。这是最简单的选择,而且你似乎不需要其他东西。 - Affe
@OneToMany一侧的@JoinColumn全部删除,你指的是@ManyToOne一侧,对吗? - nbro
@nbro,对于OneToMany/ManyToOne关系,你根本不需要JoinTable。在这种情况下,“拥有”对象(在本例中为航空公司ID)的单个FK在Many端(AirlineFlight)中由mappedBy处理即可。JoinTable仅适用于需要完全分离两侧的ManyToMany关系。 - adammtlx

39

表关系与实体关系

在关系型数据库系统中,一对多的表关系如下:

一对多的表关系

请注意,关系基于子表中的外键列(例如post_id)。

因此,在管理一对多的表关系时有一个单一的真相来源。

现在,如果您采用了映射到我们之前看到的一对多表关系的双向实体关系:

双向一对多的实体关联

如果您查看上面的图表,您会发现有两种方法可以管理此关系。

Post实体中,您有comments集合:

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

PostComment中,post关联被映射如下:

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

由于外键列有两种表示方式,因此在将关联状态更改转换为相应的外键列值修改时,必须定义哪种方式是真相源。

MappedBy

mappedBy 属性说明 @ManyToOne 端负责管理外键列,集合仅用于获取子实体和将父实体状态更改级联到子实体(例如,删除父实体也应该删除子实体)。

同步双向关联的两个方向

即使您定义了 mappedBy 属性并且子端 @ManyToOne 关联管理外键列,仍然需要同步双向关联的两个方向。

最好的方法是添加这两个实用方法:

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

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

addCommentremoveComment方法确保了双方同步。因此,如果我们添加一个子实体,则子实体需要指向父实体,而父实体应该在子集合中包含该子实体。


请问您能否详细说明一下实用方法,我有点困惑为什么我们需要同步,因为在正确映射之后,真相的来源只在多方,即PostComment。难道不是在addComment中我们已经在评论对象中设置了帖子吗?此外,如果我们只对“评论”作为单独实体执行删除,那就足够了,对吗? - Harsh Gundecha
1
本文解释了为什么需要拥有实用方法。文章链接:https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/ - Vlad Mihalcea

26

mappedby本身已经表明,它告诉Hibernate不要映射这个字段,因为已经由该字段[name="field"]映射了。这个字段在另一个实体中 (类中的变量名而不是数据库中的表名)

如果不这样做,Hibernate将把这两个关系映射为不同的关系。

因此,我们需要告诉Hibernate仅在一侧进行映射,并在它们之间协调。


"mappedBy"是可选的吗?因为我没有使用"mappedBy",但我仍然得到了相同的结果,即双向对象映射。 - Derrick
你不能在没有在其中一侧使用mappedBy的情况下使用on2many和many2one。同样,对于many2many,你必须在一侧使用mappedBy。 - Charif DZ
感谢您指出属性值的含义,即另一张表中字段的名称。 - Gab是好人
2
也许Hibernate并不总是能够自我解释,但当它这样做时,至少会使用标点符号。 - Alkanshel
3
对我来说,它并不是显而易见的;相反,它非常令人困惑。只需看看关于mappedByinversedBy实际上是什么的问题数量就知道了。其他ORM使用更加智能的belongsToManyhasMany属性。 - Jan Bodnar

16

mappedby="同一类别实体对象在另一个类中创建"

注意:由于一个表必须包含外键约束,因此Mapped by只能在一个类中使用。如果可以在两侧应用mapped by,则会从两个表中删除外键,而没有外键则两个表之间没有关系。

注意:它可用于以下注释: 1.@OneToOne 2.@OneToMany 3.@ManyToMany

注意:不能用于以下注释: 1.@ManyToOne

在一对一关系中:可以在映射的任何一侧执行,但仅在一个侧执行。 它将从应用了该类的表中删除外键约束的额外列。

例如。 如果我们在Employee类中对employee对象应用mapped by,则将从Employee表中删除外键。


3

"mappedBy"属性用于描述双向关联,必须在父级设置。换句话说,对于双向的@OneToMany关联,在父级上设置"mappedBy"为@OneToMany,并在由"mappedBy"所引用的子级上添加@ManyToOne。通过"mappedBy",双向的@OneToMany关联表示它反映了@ManyToOne的子级映射关系。


0
你开始使用ManyToOne映射,然后也为双向方式添加了OneToMany映射。 然后在OneToMany的一侧(通常是父表/类),你必须提到“mappedBy”(映射由子表/类完成),这样Hibernate就不会在数据库中创建额外的映射表(例如TableName = parent_child)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接