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:
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 aCollection<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 theCollection
as used by Hibernate.Give up on having the relationship be bidirectional -- pick one direction (
Game
→Team
seems the most appropriate) and nap only that relationship. Finding theGames
aTeam
is involved in then becomes an operation from your DAO etc, rather than something that's accessible from theTeam
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...
Continue along the route you're taking, but 'cheat' at the
Team
end. The properties at theGame
side should be mapped as normal many-to-one relationships; then usedmappedBy
at theTeam
end to indicate thatGame
'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
andteam2Games
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;
精彩评论