在JPA/Hibernate中建模一个一对多的关系

12

我有以下实体关系问题。 "游戏" 必须拥有两个(且仅有两个)"团队"对象。 "团队" 可以拥有多个"游戏"

就我所知,这是一种二对多的关系。然而...我不知道如何在JPA中建模。例如,我打算做这样的事情...

@Entity
public class Team extends BaseObject {
  private Long id;
  private Set<Game> games;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO) 
  public Long getId() {return id;}
  public void setId(Long id) {this.id = id;}

  @OneToMany(mappedBy = "game")
  public Set<Game> getGames() {return games;}
  public void setGames(Set<Game> games) {this.games = games;}
}

@Entity
public class Game extends BaseObject {
  private Long id;
  private Team team1;
  private Team team2;

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO) 
  public Long getId() {return id;}
  public void setId(Long id) {this.id = id;}

  @ HERE IS THE PROBLEM - WHAT ANNOTATION DO I USE?
  public Team getTeam1() {return team1;}
  public void setTeam1(Team team1) {this.team1 = team1;}

  @ HERE IS THE PROBLEM - WHAT ANNOTATION DO I USE?
  public Team getTeam2() {return team2;}
  public void setTeam2(Team team1) {this.team2 = team2;}
}

但是,正如您所看到的,我不确定如何从注释的角度将表格链接在一起。有人以前做过这样的事情吗?有任何想法或帮助吗?

非常感谢!

5个回答

6

我希望有人能提出一个很棒的解决方案,但这是一个棘手的情况,我从来没有找到很好的映射方式。你的选择包括:

  1. Change the way you model the relationship. For example, you could have something like:

    @Entity
    public class GameMembership {
       Team team;
       Game game;
       int gamePosition; // If tracking Team 1 vs Team 2 matters to you
    }
    

    and then Game has a Collection<GameMembership>, i.e. you model it as many-to-many. Game can still have convenient methods for setting Team 1 and Team 2, etc, (business logic to enforce that there are only 2 Teams, however that's done) but they map back onto the Collection as used by Hibernate.

  2. Give up on having the relationship be bidirectional -- pick one direction (GameTeam seems the most appropriate) and nap only that relationship. Finding the Games a Team is involved in then becomes an operation from your DAO etc, rather than something that's accessible from the Team itself:

    public class GameDAO {
        ....
        public Collection<Game> gamesForTeam(Team t) {
             ....
             Query q = session.createQuery("FROM Game WHERE team1 = :team OR team2 = :team");
             q.setParameter("team", t);
             return q.list();
        }
    }
    

    or something similar...

  3. Continue along the route you're taking, but 'cheat' at the Team end. The properties at the Game side should be mapped as normal many-to-one relationships; then used mappedBy at the Team end to indicate that Game 'controls` the relationship.

    public class Team {
            ...
            @OneToMany(mappedBy="team1")
            private Set<Game> team1Games;
            @OneToMany(mappedBy="team2")
            private Set<Game> team2Games;
    

    and then have a convenience property for your API (team1Games and team2Games are just for Hibernate's use):

        @Transient
        public Set<Game> getGames() {
            Set<Game> allGames = new HashSet<Game>(team1Games);
            allGames.addAll(team2Games);
            // Or use google-collections Sets.union() for bonus points
            return allGames;
        }
    

    so to callers of your class, it's transparent that there are 2 properties.


我同意...这似乎是最全面的答案。我觉得我最终会选择你的第二个选项。谢谢! - Brian

3
考虑一下,如果你的游戏有第一个玩家 - 一个团队(从团队列表中选择)和第二个玩家 - 一台计算机(从计算机列表中选择),会发生什么:
  • 你的第一个玩家将成为团队表中的外键。
  • 你的第二个玩家将成为计算机表中的外键。
如果现在用另一个"团队"替换"计算机"作为玩家,你将得到两个外键进入团队表。
我的JPA有点生疏,但我相信你可以使用@OneToOne注释来建立外键关系,就像这样:
@OneToOne(cascade = {CascadeType.ALL}, optional = false)
@JoinColumn(name = "team1")

还有第二个:

@OneToOne(cascade = {CascadeType.ALL}, optional = false)
@JoinColumn(name = "team2")

你好Laura,这对我来说很有意义。但是我想知道,你是否了解这对关系的另一面会产生什么影响..即当我们从Team对象调用Set getGames()方法时...也就是当我去调用"team.getGames"时,这个关系是否足够智能,可以检查这个团队在所有比赛中是团队1还是团队2? 希望这有意义,谢谢帮助。 - Brian
是的,这将在数据库级别完成。这是因为映射完全不同。在“games”表中,每行中有两列,其中包含来自“teams”表的id(这些是外键)-简单的一对一关系。在“teams”表中,由于一个团队可以是许多比赛的一部分,您将拥有1对多关系。这意味着将创建另一个类似于(id, game_id, team_id)的表,并且在“teams”表中,您将从此额外的表中获取id,而不是从“games”中获取id。请注意,您不需要自己管理额外的表。 - laura
嗨,劳拉,如果我没记错的话,你的额外表似乎是一个ManyToMany解决方案中的链接表。当你说团队到比赛是一对多关系时,我同意,但我不确定一对多是否通常需要一个链接表。这听起来不像是一个多对多的解决方案吗?此外,除非我记错了,否则Hibernate不会自动创建该链接表。感谢您的帮助! - Brian
1
从数据库的角度来看,这是一种多对多的关系。这是关系型数据库和面向对象编程之间的不匹配之一。 - Jim Barrows
当然,你是正确的 - 我所呈现的确实是一个ManyToMany映射(对不起,现在已经很晚了)。我不知道这种情况下映射会如何运作。由于PK在这种情况下的工作方式,我倾向于说您无论如何都需要一个额外的表。 - laura

2
你有一个@OneToMany的关系(我猜测Game与Team有一个@ManyToOne的关系),我的建议是:

使用封装来达到你的目标

@Entity
public class Team {

    private Game game1;
    private Game game2;

    private List<Game> gameList = new ArrayList<Game>();

    public void setGame1(Game game1) {
        // You can use index 0 to store your game1
        if(getGameList().size == 0)
            getGameList().add(game1);
        else
            getGameList().set(0, game1);
    }

    @Transient
    public Game getGame1() {
        if(getGameList().size() == 0)
            return null;

        return getGameList().get(0);
    }

    public void setGame2(Game game2) {
        // You can use index 1 to store your game2
        switch(getGameList().size()) {
            case 0:
                getGameList().add(null);

                getGameList().add(game2);
            break;
            case 1:
                getGameList().add(game2);
            break;
            case 2:
                getGameList().set(1, game2);
            break;
        }
    }

    @Transient
    public Game getGame2() {
        if(getGameList().size() < 2)
            return null;

        return getGameList().get(1);
    }

    @OneToMany
    @JoinColumn(name="TEAM_ID")
    public List<Game> getGameList() {
        return this.gameList;
    }

}

请注意,有时您需要手动完成工作。因此,封装可能是您问题的关键。

祝好!


1

我认为你有两个一对多的关系,而不是一个二对多的关系。


2
这是不正确的。他没有两个“sets”团队,他有两个团队。 - laura

0
这个可以吗:
@OneToMany
@JoinFormula(value = "SELECT g.id FROM game g WHERE g.homeTeam = id or g.awayTeam=id") 
private Set<Game> games;

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