Bummer! This is just a preview. You need to be signed in with a Basic account to view the entire video.
Start a free Basic trial
to watch this video
To round out our coverage of all the CRUD operations, we now turn to deleting entities with Hibernate and Spring. Here we find another opportunity to talk about collection mapping in Hibernate. In particular, we discover the implications of using lazy loading versus eager loading.
Git Command to Sync Your Code to the Start of this Video
git checkout -f s5v8
Using Github With This Course
You can complete this course entirely using code created by you on your local machine. However, if you choose to use the code I've made available to you, you have two options:
- Use the project files linked at the bottom of this page, or
- Use the Github repository I've made available (recommended)
If you choose the recommended option of using the Github repository, it can be found at
https://github.com/treehouse/giflib-hibernate
To utilize Github with this course, you can download the Github desktop client for your system or use the command line interface provided with Git.
Clone this repository to your machine using the Github desktop client, or using the following command:
git clone git@github.com:treehouse/giflib-hibernate.git
To update your local repository to match a video's starting point, you can use the git checkout
command in combination with the stage and video number. For example, to update your local repository to match the starting point of Stage 5, Video 4, you'd use the following:
git checkout -f s5v4
Notice the use of the -f option. This forces Git to override all local changes, so be aware: this will cause any changes you made to be lost.
Typo in Thymeleaf Template
In the video, in category/form.html, there is a typo in the conditional display of the delete button. The video shows a th:id
attribute, when it should be a th:if
attribute. Here is the entire <div>
tag as it should appear:
<div class="row delete" th:if="${category.id != null}">
Custom Exceptions in the Service Layer
Instead of coding the logic for preventing the deletion of a non-empty category in the controller, you can put this in the service layer. The advantage of moving this logic to the service layer would be that any client calling upon the service could leverage that same logic, whether it's the controller we're using in our MVC app or a REST controller that powers an API utilizing the same service. In either case we want the logic to be the same: prevent users from deleting categories that aren't empty.
To accomplish this, you could move your if
statement into the CategoryServiceImpl.delete
method:
@Override
public void delete(Category category) {
if(category.getGifs().size() > 0) {
throw new CategoryNotEmptyException();
}
categoryDao.delete(category);
}
The CategoryNotEmptyException
could be defined like this:
public class CategoryNotEmptyException extends RuntimeException {
// Your custom code here (or not - this is sufficient)
}
Then, in your controller, you can either use a try/catch block in place of the if block, or if you'd like to dive deeper into Spring you can configure controller methods to handle certain exception. See this blog post for more information.
-
0:00
With the update operations for both categories and
-
0:02
GIFs complete, it's time to move on to deleting entities.
-
0:05
This usually works in an application in one of two ways.
-
0:09
When users are asked to delete something, either one,
-
0:12
they're asked to confirm that they really want to delete.
-
0:15
Or two, a growing trend is that users aren't asked if they're sure.
-
0:19
But rather presented with an option to undo after clicking a delete button.
-
0:24
Both of these approaches require more advanced code than we'll cover, so
-
0:27
we won't offer either option.
-
0:30
But know that a live, functioning application should always have
-
0:34
one of these two options in case something was clicked or tapped by mistake.
-
0:39
Let's start by deleting a category.
-
0:41
Now we're gonna make a decision here that categories can
-
0:44
only be deleted when there are no GIFs present in that category.
-
0:48
That way, we don't wipe away an entire population of GIFs with one measly click.
-
0:53
If we intend on allowing our users the option of deleting a category.
-
0:57
We better make sure our HTML produces the functional ability to do so.
-
1:02
Our category form has a delete button, but a submit currently doesn't do anything.
-
1:07
Let's change this, I'm gonna open the category form.html template.
-
1:14
There it is.
-
1:15
Now if you scroll down to the delete button markup,
-
1:18
you'll notice it's a form in need of some attention.
-
1:21
Right here.
-
1:22
The form's opening tag has nothing in it, no attributes,
-
1:26
no action attribute, no method attribute.
-
1:28
Let's change that.
-
1:30
First, let's make sure that submitting the form will post to the correct URL.
-
1:35
I'll do that by adding a th:action attribute.
-
1:39
And here I'll have timely process the URL by including the at sign and curly braces.
-
1:46
Now this value needs to be /categories/ whatever the category id is /delete.
-
1:52
In order for
-
1:53
our controller method that we have in our controller to capture it correctly.
-
1:57
So I will use Thymeleaf concatenation to make that happen.
-
2:02
So this will be /categories/ whatever
-
2:06
the category id is and then /delete.
-
2:12
And finally, let me add that method attribute here and list it as a post.
-
2:19
One thing to be careful with here.
-
2:21
Remember how we're using this form that is this Thymeleaf template for
-
2:24
both adding a category and editing a category?
-
2:27
Well, when this template is used for adding a category,
-
2:30
that category won't have an id yet, so this right here will cause an error.
-
2:36
Even more, we probably don't even want a delete button to appear when a new
-
2:40
category is being added.
-
2:41
That wouldn't make much sense.
-
2:43
So let's add a th if attribute to check for a null ID and
-
2:47
I will place that right here.
-
2:49
Th if, so here I'll check to see if the categories id is null,
-
2:55
category.id and I'll check to see if it's not equal to null.
-
3:00
If it's not equal to null, then I do want to display this whole div.
-
3:05
So cool, we're all set with our markup.
-
3:08
Next, let's move to the category controller to the method that will capture
-
3:11
this form submission.
-
3:13
So I will open the CategoryController.
-
3:18
And the method we're looking for is the delete category method,
-
3:23
that should be at the bottom.
-
3:24
But we have a couple tasks here to take care of,
-
3:28
first we need to check to see if the category is empty and if so deleted.
-
3:31
In any case, we need to add a flash message and
-
3:34
redirect to the appropriate page.
-
3:35
So first, let's grab the category object using the category services
-
3:40
findById method.
-
3:42
So right here, I'll do that actually above this comment,
-
3:49
I'll call it cat and let's use the category services again.
-
3:54
FindById method and there's the categoryId right there, so
-
3:57
let's use that parameter value categoryId, perfect.
-
4:03
Next, let's make sure to account for
-
4:06
the possibility that the category is not empty.
-
4:10
So I'll say if(cat.getGifs().size() is greater than zero.
-
4:19
So here, if it's greater than zero,
-
4:21
I don't want to allow that category to be deleted.
-
4:25
So what I'll do here is I'll add a flash message
-
4:28
that says something to the effect of only empty categories can be deleted.
-
4:32
So I'll use that redirectAttributes.
-
4:35
And you'll notice here,
-
4:36
I don't have a parameter that is a redirectAttributes parameter.
-
4:39
Let's go add one.
-
4:43
RedirectAttributes, right, now it's there.
-
4:48
So here, to this object I will add a Flash Attribute called
-
4:54
flash and into it, I'll create a new flash message object.
-
4:58
New Flash Message.
-
5:01
And remember we put the text of the message first and then the status.
-
5:04
So only empty categories can be deleted, all right.
-
5:12
And we will use the status of FAILURE on that one.
-
5:20
And if that happens, let's redirect back to that edit form.
-
5:26
So we'll say return String.format and
-
5:30
redirect:/, that'll be categories/
-
5:35
whatever the categorId happens to be, /edit.
-
5:42
We'll get that categoryId from the parameter value here.
-
5:46
CategoryId, cool.
-
5:48
Now if we make it past this if block,
-
5:50
let's use the categoryService to delete that category.
-
5:55
We called it cat.
-
5:58
And we should also add a success message as a flash message.
-
6:03
So we'll do that again, redirectAttributes.addFlashAttribute,
-
6:08
we called it flash.
-
6:10
New FlashMessage and we'll say Category deleted.
-
6:19
And that'll have a status of SUCCESS.
-
6:26
Add my semicolon there, wonderful.
-
6:28
And now let's redirect the browser back to the /URI instead of returning null here.
-
6:34
So I'll redirect to /categories.
-
6:39
Now let's check our service and
-
6:40
Dao implementations to see what is left to code.
-
6:44
So let me open categoryServiceImpl.
-
6:48
CategoryServiceImpl, now it looks like our delete method isn't finished yet,
-
6:53
so let's call upon the Dao to do its work here.
-
6:57
So we have an override categoryDao.
-
7:00
So let's grab that and
-
7:02
call it delete method, passing the object we receive as a parameter value.
-
7:07
Now before we head to the categorDaoImp,
-
7:10
one word about this service implementation.
-
7:12
Back in the controller we check for non-empty categories.
-
7:16
There's plenty of reason to do this in the service layer instead of the controller
-
7:20
like we did.
-
7:20
And I encourage you to experiment with this.
-
7:23
For example, you might check for non-empty categories here in this method.
-
7:28
And if you find one, throw a category not empty exception,
-
7:32
which would be a short class that you would write.
-
7:35
Then, in your controller,
-
7:36
you could surround your service call with a try catch.
-
7:39
With this set up, you wouldn't have to duplicate the check for
-
7:42
non-empty categories if you want to reuse this service to power say an API as well.
-
7:49
Check the teachers notes for more information on that approach.
-
7:53
Okay, let's switch now to the Dao implementation.
-
7:56
So in the dao package, I will open CategoryDaoImpl and
-
8:00
let's focus on this delete method here.
-
8:04
In here we'll stick our normal transactional code.
-
8:06
That is we'll open the hibernate session.
-
8:13
We'll begin a transaction, we'll delete the category.
-
8:23
We'll commit the transactions, so session.getTransaction().commit().
-
8:29
And then we'll close the session.
-
8:33
Okay, with code written all the way from the UI to the persistence or
-
8:38
dao layer, it's time to test this feature.
-
8:41
Let's restart the app, I'm gonna open my panel here.
-
8:45
I'm gonna kill the previous instance of this application and
-
8:48
rerun that bootRun task.
-
8:53
Looks like everything compiled successfully and
-
8:56
let's see if we start successfully.
-
8:57
Cool, let's switch to Chrome now.
-
9:01
Now when Chrome, I want to navigate to a category without any GIFs and
-
9:05
click delete.
-
9:06
So I'm gonna go to categories and let's go to Validation,
-
9:09
I don't think I have any GIFs in here.
-
9:12
And I'm going to click Delete.
-
9:15
Now, low and behold, we get an error.
-
9:17
Now check out this message here, it says,
-
9:21
failed to lazily initialize a collection of GIFs.
-
9:28
Keep that message in mind and
-
9:29
let's flip back to IntelliJ to see if we can see what happened.
-
9:36
In IntelliJ, if I scroll up to the point in my console
-
9:40
to where the exception was first displayed.
-
9:45
I find that the culprit in my source code
-
9:48
is located in the CategoryController on line 130.
-
9:52
I'm going to click on that and
-
9:54
this is the line that is acting as the culprit in this case.
-
10:00
Now what that error message was saying is that a collection
-
10:03
in this category entity right here was not initialized.
-
10:08
What collection is that?
-
10:09
Well, it looks like this collection right here, let me show you what's happening.
-
10:14
I'm gonna open the Category entity.
-
10:17
In this Category class, we have a mapped collection named gifs.
-
10:23
Now, this collection by default is configured to
-
10:27
not be initialized when a Category entity is fetched from the database.
-
10:31
This is called lazy loading.
-
10:34
It turns out that in this OneToMany annotation here,
-
10:37
there is another element that you can specify here called fetch.
-
10:43
Now the default value of the fetch element is indeed fetchtype.lazy.
-
10:48
Now you can change this to fetchtype.eager,
-
10:52
which means that every time one or more categories are fetched from the database.
-
10:56
Another query will be performed to fetch all GIF
-
10:59
entities from the database that are associated with that category.
-
11:03
Now, a word of caution one eager fetching.
-
11:06
If Gif lib has many categories and each category has many Gifs,
-
11:12
then getting a list of all categories would involve executing more queries.
-
11:17
To fetch all the Gifs associated with every category,
-
11:20
which we aren't even using in the category index page.
-
11:24
This would be a huge waste of resources and
-
11:26
would make our users wait a long time for request to process.
-
11:30
Fortunately, there's a better option for
-
11:32
our scenario, we can simply initialize the collection when we need to.
-
11:37
Let's go back to the CategoryController to remind ourselves
-
11:40
of where this Category entity came from to begin with.
-
11:43
So in the CategoryController, we got this category object
-
11:48
from a service call to the findbyId method.
-
11:53
The way our application is coded right now, it makes enough sense to fetch
-
11:57
all the GIFs for a category, only when a single category is being fetched.
-
12:01
So all the way down in our Dao layer, we can tweak the findById method as follows.
-
12:07
Let me go to the CategoryDaoImpl.
-
12:11
If I scroll to the findById method, here is where we'll need to make those changes.
-
12:17
Before I close the session,
-
12:19
I'm going to call Hibernate.initialize on that collection of GIFs.
-
12:25
Category.getGifs.
-
12:30
What this will do is it will make sure that that Gifs collection
-
12:34
is initialized before this session is closed.
-
12:38
We were seeing that error because we tried to access a mapped collection
-
12:43
outside of a hibernate session, which just isn't possible.
-
12:49
So with that in place, let's test this again.
-
12:52
I'm going to kill this app and I'm going to rerun that bootRun task.
-
13:05
And it looks like our applications started successfully, so
-
13:07
let's go back to the browser.
-
13:10
I am just going to hit the back button to go back to that edit form and
-
13:14
hit Delete and see what happens this time.
-
13:18
Hey, look at that category was indeed deleted.
-
13:21
Now if you recall the name in that category was Validation and
-
13:24
I no longer see that category here.
-
13:26
And in addition,
-
13:27
I see this nice flash message indicating that the delete successfully worked.
-
13:34
Now of course, we'd wanna test this with the non-empty category as well, so
-
13:37
let's find one to do that.
-
13:40
I believe we had one in Technology, so let's delete that one.
-
13:45
And there we have it,
-
13:47
that flash message indicating that only empty categories can be deleted.
-
13:52
So that Technology category is still here if I go back to the list of categories,
-
13:57
we have not lost that Technology category.
-
14:00
And we have successfully prevented the deletion of categories that contain Gifs.
-
14:07
And there you go, deleting categories, done.
-
14:10
It's time for us to move onto deleting the Gifs themselves, prepare for
-
14:13
another coding task.
You need to sign up for Treehouse in order to download course files.
Sign up