The first relationship we implement will be between the Event
and EventCategory
classes. We will allow multiple events to be in the same category, but each event will only have one category. Thus, this will be a many-to-one relationship.
Before diving in, let’s reduce repetition in our persistent classes, that is, let’s DRY out our code.
AbstractEntity
- Video¶Note
The starter code for this video is found at the add-persistent-category branch of the coding-events-demo
repo.
The final code presented in this video is found on the abstract-entity branch. As always, code along to the
videos on your own coding-events
project.
AbstractEntity
- Text¶The steps needed to implement AbstractEntity
are outlined in Studio: Creating an AbstractEntity.
We are now ready to create a relationship between Event
and EventCategory
.
Note
The starter code for this video is found at the abstract-entity branch of the coding-events-demo
repo.
The final code presented in this video is found on the many-to-one branch . As always, code along to the
videos on your own coding-events
project.
Our many-to-one relationship is established in the Event
class; each event will have one category, and there may be many events in a given category.
Currently, similar functionality is enabled via the EventType
field of Event
. However, EventType
is an enum, which means that new values can not be added without changing the code and re-compiling. Using the persistent EventCategory
class to organize events will be a much more flexible and user-friendly approach.
EventType
With EventCategory
¶In the Event
class, replace the type
field with a new private field of type EventCategory
:
25 | private EventCategory eventCategory;
|
This will introduce compiler errors wherever type
is referenced. We will fix these shortly.
First, however, update Event
so that:
EventCategory
and uses it to initialize eventCategory
.eventCategory
.eventCategory
field has the @NotNull
validation annotation, along with an appropriate message.This establishes a relationship between the two classes, but that relationship is not yet persistent. To configure Hibernate to keep track of this relationship in the database, we add the @ManyToOne
annotation to eventCategory
.
27 28 29 | @ManyToOne
@NotNull(message = "Category is required")
private EventCategory eventCategory;
|
This annotation informs Hibernate that there can be many events for each category, but only one category per event.
Before we can start up the app and test our changes, we need to remove the code that references the deleted type
field. A quick way to find these references is to find EventType
in the Project pane of IntelliJ, right-click, and select Find Usages. This will open a pane listing each location where EventType
occurs in our project, whether that is an object of that type or a direct reference to the class itself.
The first occurrence is in EventController.displayCreateEventForm
:
35 | model.addAttribute("types", EventType.values());
|
This line passes a collection of all of the values of EventType
into the view, to be rendered in the form used to create new events.
Since we are now using EventCategory
to group events, our code should instead be passing in all of the category objects in our app. To fetch category objects, we need an instance of EventCategoryRepository
in our controller. Add an @Autowired
instance to the top of the controller:
24 25 | @Autowired
private EventCategoryRepository eventCategoryRepository;
|
Now, use the repository to fetch all saved categories:
40 | model.addAttribute("categories", eventCategoryRepository.findAll());
|
This line replaces the line references EventType.values()
. Notice that we have relabeled this attribute "categories"
to be more consistent. This also requires updating the events/create.html
template:
27 28 29 30 31 32 33 34 35 36 37 | <div class="form-group">
<label>Category
<select th:field="${event.eventCategory}">
<option th:each="eventCategory : ${categories}"
th:value="${eventCategory.id}"
th:text="${eventCategory.name}"
></option>
</select>
<p class="error" th:errors="${event.eventCategory}"></p>
</label>
</div>
|
This new template code includes several changes:
select
is now bound to the field eventCategory
.categories
and has an updated iterator variable name, eventCategory
.select
is now eventCategory.id
. This allows model binding to occur upon form submission. Spring Boot will determine the category object to assign to the new event object by referencing the id
.option
is now eventCategory.name
event.eventCategory
.The remaining usages of EventType
refer to:
Event
constructor parameter.type
in Event
.EventController
.EventType
class itself.Removing this unneeded code resolves all remaining compiler errors.
The events/index.html
template needs to be updated as well, since it still contains a reference to the type
field of Event
:
23 | <td th:text="${event.type}"></td>
|
This usage wasn’t found by IntelliJ because templates do not receive compile-time type checking like classes do. The updated version looks like this:
23 | <td th:text="${event.eventCategory.name}"></td>
|
Before starting up the application, let’s look at our event
table:
Notice that the type
column remains, even though we have deleted the type
field from the Event
class. The next time we start up the application, Hibernate will attempt to update the schema to match the structure of our updated model class.
Tip
Notice that existing events will not have a category, which violates our new @NotNull
validation rule. The easiest way to handle this is to delete all rows in event
.
To delete all events, truncate the event
table in MySQL Workbench. (To truncate a SQL table is simply to delete all its rows.) To do this, select the table in the Schemas pane, right-click, and select Truncate Table…
Start up the application and refresh the view in MySQL Workbench.
Notice that there is a new column, event_category_id
. This new column has type int
and is a foreign key column to the event_category
table. References to objects in Java are translated into foreign-key references by Hibernate.
Note
You may find it odd that the type
column is still present, even after restarting. Hibernate will not drop columns from a table even if that field is removed from the corresponding class.
Hibernate will generally avoid deleting your data, since you may want to keep it around (even if just in the form of a backup). You can simply drop this column yourself.
Tip
If your table was not updating upon restarting, make sure you have spring.jpa.hibernate.ddl-auto
set to update
in application.properties
.
If we create some new events, we can see precisely how event
rows are related to event_category
rows.
Notice that our events have event_category_id
values of 12
and 13
(these values may differ slightly in your application). These are foreign keys into the event_category
table. For example, in the tables above, the event
row with id
equal to 14
is related to the event_category
row with id
equal to 12
. This database relationship corresponds to the relationship between objects that was created by Spring Boot when we submitted the form.
With this many-to-one relationship in place, we next look at how to establish a persistent one-to-many relationship.
Question
What is the annotation that allows @AbstractEntity
to handle logic related to IDs/primary keys of subclasses,
yet not be mapped to a database table.
Question
You are working on a Spring application tracking elected officials. Your model class, Senator
has a many-to-one relationship
with another model class, State
. To properly configure this relationship in the Hibernate context, what must be present?
Senator
, a state
field, annotated with @OneToMany
Senator
, a state
field, annotated with @ManyToOne
State
, a senator
field, annotated with @OneToMany
State
, a senator
field, annotated with @ManyToOne