Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 - WHAT 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!

like image 448
Brian Avatar asked Feb 01 '10 15:02

Brian


1 Answers

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.

like image 94
Cowan Avatar answered Sep 18 '22 10:09

Cowan