开发者

Modelling a Two to Many Relationship in JPA/Hibernate

I have the following entity relationship problem. A "Game" must have two (and only two) "Team" objects. A "Team" can have many "Games"

This, as far as I can see is a Two-to-Many relationship. However...I don't know how to model this in JPA. Eg, I was going to do something like this...

@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 - WH开发者_C百科AT ANNOTATION DO I USE?
  public Team getTeam2() {return team2;}
  public void setTeam2(Team team1) {this.team2 = team2;}
}

But, as you can see, I'm not sure how to link the tables together from an annotation side. Has anyone ever done something like this before? Any ideas, help?

thanks very much!


I would love for someone to come up with an awesome solution to this, but it's a tricky situation which I've never been able to find a way to map very well. Your options include:

  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.


Consider what would happen if your games would have the first player - a team (chosen from a list of teams) and the second player - a computer (chosen from a list of computers):

  • Your first player would be a foreign key into the teams table.
  • Your second player would be a foreign key into the computers table.

If you now replace the "computer" as a player with another "team", you would get two foreign keys into the teams table.

My JPA is a bit rusty, but I believe you model a foreign key relationship with a @OneToOne annotation, like so:

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

and the second one with:

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


You have a @OneToMany relationship (I suppose Game has a @ManyToOne relationship with Team), my advice is:

Use encapsulation to get your goal

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

}

Be aware sometimes you have to do your job mannually. So encapsulation can be the key to your question.

regards,


I think you have two one-to-many relationships, not one two-to-many.


Would this work:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜