In the last part of the tutorial, we spent some time with the basic setup of the project and wrote our first classes that we're going to use as JPA entities. You might recall the simple entity/relationship diagram from the last part that showed how these classes, that JPA will map to database tables for us, are related to each other. Let's recap:
- A game can have many results
- A result belongs to exactly one game and to exactly one player
- A player can have many results
The GameResult class is what actually binds games and players together. The player that participated in a game can easily be determined by checking the results of the game, as each of these is associated with a player. This relationship can be used the other way round to check in which games a player participated. So now it's time to tell JPA about the relationships.
Getting the sources
As always, you can either go the project website or checkout the associated tag from the source control system:
svn checkout http://jpokerstats.googlecode.com/svn/tags/tutorial_part_03
Establishing relationships
First, we add two new field together with getters and setters to the GameResult class. One of it holds an instance of Game, the other an instance of Player, so the the result can tell us to which game and to which player it belongs. In more technical terms, GameResult has a many-to-one relationship with Game and Player. The way JPA is designed, this forces us to declare GameResult the owner of the relationship. This can be done by annotating the appropriate getters with @ManyToOne. The target entity classes will be resolved by JPA automatically by evaluating the field type.
-
private Player _player;
-
-
private Game _game;
-
-
@ManyToOne
-
public Player getPlayer()
-
{
-
return _player;
-
}
-
-
public void setPlayer(final Player player)
-
{
-
_player = player;
-
}
-
-
@ManyToOne
-
public Game getGame()
-
{
-
return _game;
-
}
-
-
public void setGame(final Game game)
-
{
-
_game = game;
-
}
As we have many-to-one relationships, both Player and Game must declare a collection of GameResult objects. In each of these classes, we introduce a field of type Set<GameResult> and add getters and setters for it. As you are free to name your methods as you like, we need to give JPA a hint on how to connect the relationships. So we annotate the getters with @OneToMany and specify the mappedBy attribute. This is a string that tells JPA which property on the related class is the counterpart for this relationship.
-
private Set<GameResult> _results;
-
-
@OneToMany(mappedBy="game")
-
public Set<GameResult> getResults()
-
{
-
return _results;
-
}
-
-
public void setResults(Set<GameResult> results)
-
{
-
_results = results;
-
}
Why using the mappedBy attribute?
Using the mappedBy attribute may seem unecessary at first glance but think about a situation where you have entities A and B. A is a person record and B a transaction record. B references two person records at once, e.g. in a buyer/seller situation. Now B suddenly has two fields of type A and JPA cannot determine, which belongs to which relationship. You can solve the situation by using the mappedBy attribute to make the relationships unique again.
Why using sets instead of lists?
Reading a tutorial like this I'd assume you know why, but let me tell you. There is only a single logical difference between a list and a set: a list has an order whereas a set has not. But this simple logical difference is a big technical one: append, insert and remove operations on list cause far more overhead than they do on sets, so sets are a lot faster. In this example, we also do not need the order of elements in the sets as we will re-sort them according to the users preferences in the web frontend later on. If you do not believe me, you could write you own test program. Although I have to say that the increase in performance by using sets instead of lists will be eaten up by the "black magic" that Spring, Facelets and all the others use to get the application working. I guess, everything comes at a price, but that's fine with me. If it were otherwise, I would write everything myself like most C people do.
Saving us trouble
The are a lot of things to stumble or even fall over when working with JPA. In order to avoid mistakes right from the start, I need to explain some things and add some extra code.
First of all, we'll not be working with whole sets of objects at once, so we add extra methods to work with single objects at a time:
-
public void addResult(final GameResult result)
-
{
-
_results.add(result);
-
}
-
-
public void removeResult(final GameResult result)
-
{
-
_results.remove(result);
-
}
You might have noticed another problem. What if the set is not initialized? To avoid countless NPEs, we add a constructor to Game and Player and make sure the set gets initialized.
-
public Game()
-
{
-
_results = new HashSet<GameResult>();
-
}
One big source of errors when working with JPA revolves around object identity. The question here is: Under what conditions are two objects treated as equal? Let's assume you create 5 Player instances, set the names to John Doe and add them to a set. You end up with 5 John Does in a set. This is not what we want, we want only one John Doe in the set. This gets even worse if you load the same record from the database twice. Although it is the same record, the objects will no be treated as such. If one is already in a list and you ask Java: "Hey, is there a John Doe in there?" and use the other object, Java will respond: "No!". This will get you into serious trouble and from my experience, 80% of all problems in the persistence layer of a project are caused by identity problems. To fully understand the seriousness of identity problems, consider the US no-fly lists.
There is a book called Effective Java: Language Programming Guide. It is quite old now, but its contents remain valid even today. I recommend reading chapter 8: Always override hashCode() when you override equals(), it describes a solution to our problem. For further reading please refer to the articles on equals() (1, 2) by Angelika Langer and Klaus Kreft. Readers capable of reading German can read the articles in German: 1, 2, 3.
After reading this, we'll implement the equals() and hashCode() methods for all entity objects. It is important not to include the dataset id in the hash, as it is not part of the semantic content of a record. Most likely you IDE can generate those methods for you. Below is an example for the Game class:
-
@Override
-
public int hashCode()
-
{
-
final int offset = 11;
-
final int multiplier = 37;
-
int hashCode = offset;
-
-
// long values will be split into two integers
-
hashCode += (int)(_buyinChips>>>32) * multiplier;
-
hashCode += (int)(_buyinChips & 0xFFFFFFFF) * multiplier;
-
hashCode += (int)(_buyinCosts>>>32) * multiplier;
-
hashCode += (int)(_buyinCosts & 0xFFFFFFFF) * multiplier;
-
hashCode += (int)(_rebuyChips>>>32) * multiplier;
-
hashCode += (int)(_rebuyChips & 0xFFFFFFFF) * multiplier;
-
hashCode += (int)(_rebuyCosts>>>32) * multiplier;
-
hashCode += (int)(_rebuyCosts & 0xFFFFFFFF) * multiplier;
-
-
// reference values have to be null-tested
-
if (null != _date) hashCode += _date.hashCode() * multiplier;
-
if (null != _startTime) hashCode += _startTime.hashCode() * multiplier;
-
if (null != _endTime) hashCode += _endTime.hashCode() * multiplier;
-
if (null != _gameType) hashCode += _gameType.hashCode() * multiplier;
-
-
return hashCode;
-
}
-
-
@Override
-
{
-
if (null == other || !(getClass().equals(other.getClass())))
-
return false;
-
-
final Game otherGame = (Game) other;
-
-
if (null == _date)
-
if (null != otherGame.getDate())
-
return false;
-
-
if (null == _endTime)
-
if (null != otherGame.getEndTime())
-
return false;
-
-
if (null == _startTime)
-
if (null != otherGame.getStartTime())
-
return false;
-
-
if (null == _gameType)
-
if (null != otherGame.getGameType())
-
return false;
-
-
// we start off assuming the objects are equal and use AND
-
// operations for the comparisons
-
boolean equals = true;
-
equals &= getBuyinChips() == otherGame.getBuyinChips();
-
equals &= getBuyinCosts() == otherGame.getBuyinCosts();
-
equals &= getRebuyChips() == otherGame.getRebuyChips();
-
equals &= getRebuyCosts() == otherGame.getRebuyCosts();
-
-
if (null != getDate())
-
equals &= getDate().equals(otherGame.getDate());
-
-
if (null != getStartTime())
-
equals &= getStartTime().equals(otherGame.getStartTime());
-
-
if (null != getEndTime())
-
equals &= getEndTime().equals(otherGame.getEndTime());
-
-
if (null != getGameType())
-
equals &= getGameType().equals(otherGame.getGameType());
-
-
return equals;
-
}
I added some unit tests to make sure that these methods actually work and that the contract for equals() and hashCode() is not violated.
What's next?
The next part of this tutorial will deal with even more testing. Now that hashCode() and equals() are implemented, it's time to see if our model works when used in a real database. We will create some basic tests using HSQLDB and let Spring do the configuration work for us.





