views:

294

answers:

4

Hi folks,

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!

A: 

Hi

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

Regards

Mark

High Performance Mark
This is incorrect. He doesn't have two `sets` of teams, he has two teams.
laura
+1  A: 

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")
laura
Hi Laura,that makes sense to me. However, I wonder do you know what impact this has to the other side of the relationship..ie, from the Team object, when we call the Set<Game> getGames()method...ie, when I go to call "team.getGames" will the relationship be smart enough to check if this team is either team1 or team2 in all the games?Hope this makes sense and thanks for the help.
Brian
Yes it will - done at database level. This is because the mapping is completely different. In the `games` table you would have two columns in each row which have in them an id from the `teams` table (these are foreign keys) - simple one-to-one. In the `teams` table, since a team can be part of many games, you will have a 1-to-may relationship. This means that another table will be created which will look similar to `(id, game_id, team_id)` and in the `teams` table you will have the `id` from this extra table, not the `id` from `games`. Note that you don't manage the extra table yourself.
laura
Hi Laura, your extra table seems to be a link table in a ManyToMany solution if I'm not mistaken. When you say that the teams to games is a 1 to many relationship, I agree, but I'm not sure if a one to many requires a link table normally. Doesn't this sound like a many to many solution? Also, that link table won't be created automatically by hibernate unless I'm mistaken. thanks for the help!
Brian
From the database point of view, this is a ManyToMany relationship. This is one of the mismatches between relational databases and OOP.
Jim Barrows
You are right of course - what I presented is indeed a ManyToMany mapping (it's late in the day for me, sorry). I don't know how the mapping will act in this case. I am tempted to say you will be needing an extra table anyway due to how the PKs work in this instance.
laura
@Jim, thanks for clearing that up.
laura
+1  A: 

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.

Cowan
I agree...this seems the most comprehensive answer. I think I'm going to end up having to go with your second option. Thanks!
Brian
A: 

Hi,

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,

Arthur Ronald F D Garcia