In the examples in the previous part of the tutorial, we looked at how simple JPA could make accessing the data in our database. Now we are going to explore how to configure JPA to give us this functionality.
In the next tutorial, we will discuss the persisting of data (e.g.: saving data to the database). So don’t get frustrated that we don’t cover this immediately–your patience will be rewarded!
Overview
In this installment we will show you how to annotate the following types of relationships
- Bidirectional One-to-Many / Many-to-One
- Unidirectional One-to-Many
- Unidirectional Many-to-One
- Bidirectional Many-to-Many
These four relationships should cover the bulk of use cases for most applications. But before we dive into these annotations, let’s review the database schema for the application.
One-to-Many / Many-to-One bidirectional relationship :
Comment ↔ Attachment
The relationship of Comments
to Attachments
is a One-to-Many relationship in the Comment → Attachment
direction (since every Comment
can contain many Attachments
), and is a Many-to-One relationship in the Attachment → Comment
direction (since every Attachment
is contained in a single Comment)
. Now, since this is JPA, we would like to be able to write code like what we have below :
One-to-Many : Comment → Attachment direction
(‘contains’ role)
// Get access to a Comment object Comment comment = em.find(Comment.class, commentId); // Get a list of the many attachments contained in this Comment List<Attachment> attachments = comment.getAttachments(); Listing 1
Many-to-One : Attachment → Comment direction
(‘contained in’ role)
// Get an Attachment object Attachment attachment = em.find(Attachment.class, attachmentId); // Get the one Comment that it is contained in Comment comment = attachment.getContainerComment(); Listing 2
Note that, because we will be defining methods for both sides of this relation (e.g.: from a Comment
object, we can get a list of its attachments, and, from an Attachment
object we can get its comment), we refer to this relationship as bidirectional.
In the diagram above, we see that it is the Many side of the relationship (attachment) that holds a foreign key to the One side (comment). Specifically, the attachment table contains the commentId
foreign key that associates it with the comment it is contained in. In JPA terminology, this makes Attachment
the owning side of this bidirectional relationship. Conversely, the other end of the relationship is called the inverse side (e.g.: non-owning side) of the relationship.
Entity Annotations
Many-to-One : Attachment → Comment direction
Let’s start by annotating the owning side of the relationship (the Attachment
entity). The first thing we need to do is create a variable to hold the Comment
entity this Attachment
is contained in.
(in the Attachment entity - owning side) // holds the Comment this Attachment is contained in private Comment containerComment; Listing 3
The next thing we do is create the accessors for the comment
variable, and annotate the getter :
(in the Attachment entity - owning side) @ManyToOne(cascade = CascadeType.PERSIST) @JoinColumn(name="commentId") public Comment getContainerComment() { return containerComment; } public void setContainerComment(Comment containerComment) { this.comment = containerComment; } Listing 4
The first annotation @ManyToOne tells JPA that this is a Many-to-One relationship with the Comment
entity (JPA automatically infers that this is a relationship with the Comment
entity, since the getter we’re annotating returns a Comment
). As for the cascade = CascadeType.PERSIST code–we will get into that in the next section where we discuss persistence.
The second annotation is @JoinColumn. The name
element of @JoinColumn always tells JPA what column of the owning side is the foreign key for the relationship. Since Attachment
is the owning side, the value commentId
is a column of the attachment table that references the comment
table.
One-to-Many : Comment → Attachment direction
OK. So we’ve annotated the Attachment → Comment
relationship (e.g.: the ‘contained in’ role) on the Attachment
side. We will now annotate the inverse Comment → Attachment
‘contains’ role.
As with the Attachment side, we need to create a variable on the Comment
side to hold the elements joined by the relationship. However, in this case, a single Comment
may be related to multiple Attachments
(since a comment can contain multiple attachments). For this, we need to use a Collection interface. We could use a Set, List, Map etc… it just has to extend the java.util.Collection
interface.
(in Comment entity - inverse side) // stores the Attachments that this Comment contains private List<Attachment> attachments; Listing 5
The next step is to (like before) create the accessors, and then annotate the getter :
(in Comment entity - inverse side) @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "containerComment") public List<Attachment> getAttachments() { return attachments; } public void setAttachments(List<Attachment> attachments) { this.attachments = attachments; } Listing 6
Here we only have one annotation. As usual, we have the cascade
element (which will be covered later), but we also have mappedBy
.The mappedBy
element is only ever used on the inverse side of a bidirectional relationship. The value of mappedBy
tells JPA the name of the variable on owning side that holds the inverse-side-entity. In this case, the value of mappedBy
is the containerComment
variable in the Attachment
object. JPA is able to infer that containerComment
is in the Attachment
class since the getter we annotate returns a collection of Attachment
entities.
But why do we have to specify the specific variable that holds a reference to this Comment
? Well, if we didn’t, JPA would assume that Comment
was the owning side of the relationship and expect that the foreign key for the relationship was in the Comment
entity–which it is not.
One-to-Many unidirectional relationship :
User → Channel
In this section, we’re going to create a unidirectional One-to-Many relationship from User to Channel. This is a “created by” role, since–given a user u
–we can retrieve all of the channels that u
created.
Once we configure JPA to be aware of this relationship, then we will be able to write code like the following :
// find a user via its userId User user = em.find(User.class, userId); // Get a list of the channels it has created List<Channel> usersChannels = user.getChannels(); Listing 7
Note that, since this is a unidirectional relationship, we won’t be able to write code like
User creator = channel.getCreator();
since the relationship is only accessible from one side (the User
entity in this case).
So, how do we inform JPA of this unidirectional One-to-Many “created by” relationship between User
and Channel
?
Entity Annotations
To inform JPA of the One-to-Many relationship between User
and Channel
, the first thing we do is create a private collection variable to hold the channels the user has created :
(in the User entity - inverse side) // Collection to hold all the channels this user created private List<Channel> createdChannels; Listing 8
We then create accessors (a getter and a setter) for the createdChannels
collection, and annotate its getter with information about the One-to-Many relationship :
(excerpt from the User entity - inverse side) @OneToMany(cascade = CascadeType.PERSIST) @JoinColumn(name="userId") public List<Channel> getCreatedChannels() { return createdChannels; } public void setCreatedChannels(List<Channel> createdChannels) { this.createdChannels = createdChannels; } Listing 9
The first @OneToMany annotation tells JPA that this is a One-to-Many relationship with a cascade type of PERSIST. The mappedBy
element in the @JoinColumn
annotation tells JPA that the name of the foreign key for this One-to-Many relationship is in the channel table and that its name is userId
. But, how does JPA know that it needs to look for the foreign key in the channel table? Well, as we learned before, the value of the name element in the @JoinTable tag always refers to a foreign key on the owning side of a relationship. Therefore JPA knows it needs to look at the table on the other end of the relationship for the foreign key. But how does JPA know which table is on the other end? Well, the fact that the @OneToMany annotation is placed over a getter that returns a Collection of Channel
objects allows JPA to infer that the table on the other end is the channel table.
OK, so that was quite a bit of explanation for a tiny bit of code and annotations. You’ll find this a lot with JPA–with only a few lines of code, you can get JPA to do a lot of deep things–you just have to know what you’re doing.
Having annotated the User
entity, the code in listing 7 will now work; with JPA doing all the database queries, all the conversion from SQL data into Java objects, and automatically populating the channels
List variable with Channel
objects as needed.
Many-to-One unidirectional relationship :
Comment → User
In the previous example, we outlined how to create a unidirectional One-to-Many relationship. Here we will show how to create a unidirectional Many-to-One relationship, from Comment → User. Comment → User is Many-to-One since every Comment has exactly one user that authors it, yet every user can author many comments.
Once complete, we will be able to write code like the following :
(in Comment entity) // retrieve a Comment Comment comment = em.find(Comment.class, commentId); // retrieve the Comment's author User author = comment.getAuthor(); Listing 10
The annotations for this Many-to-One, unidirectional relationship from Comment → User is straight-forward and is very similar to the other relationship annotations we’ve previously encountered.
We first create a variable to store data from the One side (User side) of the relation…
(in Comment entity) private User author; Listing 11
…and then create accessors for it, and annotate the getter :
(in Comment entity) @ManyToOne(cascade = CascadeType.PERSIST) @JoinColumn(name = "userId") public User getAuthor() { return author; } public void setAuthor(User author) { this.author = author; } Listing 12
The first @ManyToOne annotation specifies that this is a Many-to-One relation. The second @JoinColumn annotation tells JPA the name of the foreign key for this relationship. As always, the value of the name element in the @JoinTable annotation always refers to a foreign key on the owning side of the relationship, and hence, JPA knows that the “userId” value in the @JoinColumn(name = “useId”) annotation refers to a column in the comment table.
Many-to-Many bidirectional relationship :
Channel ↔ User
Here, we will describe how to annotate for a Many-to-Many relationship.
Channel ↔ User is Many-to-Many since every channel can have many member users, and every user can be a member of many channels.
(in User entity - owning side) private Set<Channel> channelMemberships; ... @JoinTable(name = "user_to_channel", joinColumns = @JoinColumn(name = "userId"), inverseJoinColumns = @JoinColumn(name = "channelId") ) @ManyToMany(cascade = CascadeType.PERSIST) public Set<Channel> getChannelMemberships() { return channelMemberships; } public void setChannelMemberships(Set<Channel> channelMemberships) { this.channelMemberships = channelMemberships; } Listing 15
As you can see, the annotations for a Many-to-Many relationship are a bit more complex than what we’ve seen up until now.
Owning side
Since a Many-to-Many relationship uses a join table, neither side has a foreign key referencing the other. This means that we get to choose which side is the owning side. In this example, we have chosen the User
entity to be the owning side.
Entity Annotations – Owning Side
The first annotation is a @JoinTable annotation. This defines the properties of the Many-to-Many join table that relates the user and channel tables. JPA will generate this table for us based on how we define the annotation.
At its simplest, a Many-to-Many join table is simply a two column table that holds pairs of primary keys–one primary key from the user table, and one primary key from the channel table. JPA needs to know (1) the name of this table, (2) what primary keys the columns of this table should hold.
In this example, we are naming the join table user_to_channel. This is specified in the name
element :
@JoinTable(name = “user_to_channel”, …).
Within the @JoinTable annotation, we specify the primary keys of the user_to_channel table in joinColumns
and inverseJoinColumns
elements.
- name : this is the name that will be given for this foreign key column in the join table
- referencedColumnName : the name of the foreign key
- inside a
joinColumns
element, this will be the name of the key on the owning side (i.e:user.userId
) - inside an
inverseJoinColumns
element, this will be the name of the key on the inverse side (channel.channelId
)
- inside a
Since the User
entity is the owning side of the relation, we set joinColumns
to the user table’s primary key :
joinColumns = @JoinColumn(name = “userId”, referencedColumnName = “userId”)
Similarly, we set inverseJoinColumns
to the primary key of the inverse (non-owning) side :
inverseJoinColumns = @JoinColumn(name = “channelId”, referencedColumnName = “channelId”)
Note : In our case, the names of the join table columns are the same as the names of the primary keys in the user and channel tables, so–in fact–we could omit the referencedColumnName
element and JPA would automatically use userId and channelId for the join table columns. However, if you need to integrate with an existing database with existing join tables, you could specify different column names for your join table.
In the second annotation, we tell JPA this is a Many-to-Many relationship :
@ManyToMany(cascade = CascadeType.PERSIST)
And that is all we need to set on the owning side.
Entity Annotations – Inverse Side
Comparatively, annotating the inverse side is much easier since we’ve already provided so much information on the owning side.
(in Channel entity) private Set<User> members; @ManyToMany(mappedBy = "channelMemberships", cascade = CascadeType.PERSIST) public Set<User> getMembers() { return members; } public void setMembers(Set<User> members) { this.members = members; } Listing 17
As you can see, we have only a single annotation on the inverse side–the @ManyToMany annotation–and its only properties are mappedBy = "channelMemberships"
and (as usual) cascade = CascadeType.PERSIST
. Similar to our other bidirectional relationship, use of the mappedBy
element on the inverse side allows JPA to find the owning side of the relation which contains the join table information–this allows us to define the join table information in just one spot.
Summary
The remainder of the relationships our application needs are all represented in the examples above. I will provide the full source code of this application on GitHub if you want to see how I’ve done the remainder–however, I’d suggest you attempt to do so yourself before perusing the code.
In the next section, we will discuss JPA persistence. To do so, we will need to get into the JPA architecture a bit more. So, until next time!