1 00:00:00,540 --> 00:00:02,750 With the update operations for both categories and 2 00:00:02,750 --> 00:00:05,920 GIFs complete, it's time to move on to deleting entities. 3 00:00:05,920 --> 00:00:09,870 This usually works in an application in one of two ways. 4 00:00:09,870 --> 00:00:12,450 When users are asked to delete something, either one, 5 00:00:12,450 --> 00:00:15,160 they're asked to confirm that they really want to delete. 6 00:00:15,160 --> 00:00:19,400 Or two, a growing trend is that users aren't asked if they're sure. 7 00:00:19,400 --> 00:00:24,370 But rather presented with an option to undo after clicking a delete button. 8 00:00:24,370 --> 00:00:27,940 Both of these approaches require more advanced code than we'll cover, so 9 00:00:27,940 --> 00:00:30,030 we won't offer either option. 10 00:00:30,030 --> 00:00:34,240 But know that a live, functioning application should always have 11 00:00:34,240 --> 00:00:38,610 one of these two options in case something was clicked or tapped by mistake. 12 00:00:39,620 --> 00:00:41,760 Let's start by deleting a category. 13 00:00:41,760 --> 00:00:44,320 Now we're gonna make a decision here that categories can 14 00:00:44,320 --> 00:00:48,150 only be deleted when there are no GIFs present in that category. 15 00:00:48,150 --> 00:00:52,530 That way, we don't wipe away an entire population of GIFs with one measly click. 16 00:00:53,840 --> 00:00:57,615 If we intend on allowing our users the option of deleting a category. 17 00:00:57,615 --> 00:01:02,750 We better make sure our HTML produces the functional ability to do so. 18 00:01:02,750 --> 00:01:07,680 Our category form has a delete button, but a submit currently doesn't do anything. 19 00:01:07,680 --> 00:01:12,050 Let's change this, I'm gonna open the category form.html template. 20 00:01:14,060 --> 00:01:15,795 There it is. 21 00:01:15,795 --> 00:01:18,175 Now if you scroll down to the delete button markup, 22 00:01:18,175 --> 00:01:21,525 you'll notice it's a form in need of some attention. 23 00:01:21,525 --> 00:01:22,515 Right here. 24 00:01:22,515 --> 00:01:26,355 The form's opening tag has nothing in it, no attributes, 25 00:01:26,355 --> 00:01:28,855 no action attribute, no method attribute. 26 00:01:28,855 --> 00:01:30,535 Let's change that. 27 00:01:30,535 --> 00:01:35,670 First, let's make sure that submitting the form will post to the correct URL. 28 00:01:35,670 --> 00:01:39,620 I'll do that by adding a th:action attribute. 29 00:01:39,620 --> 00:01:46,490 And here I'll have timely process the URL by including the at sign and curly braces. 30 00:01:46,490 --> 00:01:52,880 Now this value needs to be /categories/ whatever the category id is /delete. 31 00:01:52,880 --> 00:01:53,510 In order for 32 00:01:53,510 --> 00:01:57,960 our controller method that we have in our controller to capture it correctly. 33 00:01:57,960 --> 00:02:02,100 So I will use Thymeleaf concatenation to make that happen. 34 00:02:02,100 --> 00:02:06,922 So this will be /categories/ whatever 35 00:02:06,922 --> 00:02:11,471 the category id is and then /delete. 36 00:02:12,630 --> 00:02:18,240 And finally, let me add that method attribute here and list it as a post. 37 00:02:19,390 --> 00:02:21,048 One thing to be careful with here. 38 00:02:21,048 --> 00:02:24,640 Remember how we're using this form that is this Thymeleaf template for 39 00:02:24,640 --> 00:02:27,850 both adding a category and editing a category? 40 00:02:27,850 --> 00:02:30,700 Well, when this template is used for adding a category, 41 00:02:30,700 --> 00:02:35,330 that category won't have an id yet, so this right here will cause an error. 42 00:02:36,570 --> 00:02:40,550 Even more, we probably don't even want a delete button to appear when a new 43 00:02:40,550 --> 00:02:41,920 category is being added. 44 00:02:41,920 --> 00:02:43,470 That wouldn't make much sense. 45 00:02:43,470 --> 00:02:47,540 So let's add a th if attribute to check for a null ID and 46 00:02:47,540 --> 00:02:49,390 I will place that right here. 47 00:02:49,390 --> 00:02:55,066 Th if, so here I'll check to see if the categories id is null, 48 00:02:55,066 --> 00:03:00,200 category.id and I'll check to see if it's not equal to null. 49 00:03:00,200 --> 00:03:04,690 If it's not equal to null, then I do want to display this whole div. 50 00:03:05,750 --> 00:03:08,130 So cool, we're all set with our markup. 51 00:03:08,130 --> 00:03:11,940 Next, let's move to the category controller to the method that will capture 52 00:03:11,940 --> 00:03:13,720 this form submission. 53 00:03:13,720 --> 00:03:15,885 So I will open the CategoryController. 54 00:03:18,520 --> 00:03:23,430 And the method we're looking for is the delete category method, 55 00:03:23,430 --> 00:03:24,825 that should be at the bottom. 56 00:03:24,825 --> 00:03:28,125 But we have a couple tasks here to take care of, 57 00:03:28,125 --> 00:03:31,875 first we need to check to see if the category is empty and if so deleted. 58 00:03:31,875 --> 00:03:34,035 In any case, we need to add a flash message and 59 00:03:34,035 --> 00:03:35,915 redirect to the appropriate page. 60 00:03:35,915 --> 00:03:40,025 So first, let's grab the category object using the category services 61 00:03:40,025 --> 00:03:41,465 findById method. 62 00:03:42,585 --> 00:03:46,035 So right here, I'll do that actually above this comment, 63 00:03:49,170 --> 00:03:54,000 I'll call it cat and let's use the category services again. 64 00:03:54,000 --> 00:03:57,700 FindById method and there's the categoryId right there, so 65 00:03:57,700 --> 00:04:03,430 let's use that parameter value categoryId, perfect. 66 00:04:03,430 --> 00:04:06,060 Next, let's make sure to account for 67 00:04:06,060 --> 00:04:10,260 the possibility that the category is not empty. 68 00:04:10,260 --> 00:04:19,630 So I'll say if(cat.getGifs().size() is greater than zero. 69 00:04:19,630 --> 00:04:21,930 So here, if it's greater than zero, 70 00:04:21,930 --> 00:04:25,700 I don't want to allow that category to be deleted. 71 00:04:25,700 --> 00:04:28,350 So what I'll do here is I'll add a flash message 72 00:04:28,350 --> 00:04:32,370 that says something to the effect of only empty categories can be deleted. 73 00:04:32,370 --> 00:04:35,180 So I'll use that redirectAttributes. 74 00:04:35,180 --> 00:04:36,310 And you'll notice here, 75 00:04:36,310 --> 00:04:39,920 I don't have a parameter that is a redirectAttributes parameter. 76 00:04:39,920 --> 00:04:43,177 Let's go add one. 77 00:04:43,177 --> 00:04:47,360 RedirectAttributes, right, now it's there. 78 00:04:48,540 --> 00:04:54,000 So here, to this object I will add a Flash Attribute called 79 00:04:54,000 --> 00:04:58,990 flash and into it, I'll create a new flash message object. 80 00:04:58,990 --> 00:05:01,080 New Flash Message. 81 00:05:01,080 --> 00:05:04,880 And remember we put the text of the message first and then the status. 82 00:05:04,880 --> 00:05:12,680 So only empty categories can be deleted, all right. 83 00:05:12,680 --> 00:05:17,790 And we will use the status of FAILURE on that one. 84 00:05:20,250 --> 00:05:26,100 And if that happens, let's redirect back to that edit form. 85 00:05:26,100 --> 00:05:30,842 So we'll say return String.format and 86 00:05:30,842 --> 00:05:35,864 redirect:/, that'll be categories/ 87 00:05:35,864 --> 00:05:42,330 whatever the categorId happens to be, /edit. 88 00:05:42,330 --> 00:05:46,570 We'll get that categoryId from the parameter value here. 89 00:05:46,570 --> 00:05:48,820 CategoryId, cool. 90 00:05:48,820 --> 00:05:50,710 Now if we make it past this if block, 91 00:05:50,710 --> 00:05:55,410 let's use the categoryService to delete that category. 92 00:05:55,410 --> 00:05:56,110 We called it cat. 93 00:05:58,120 --> 00:06:03,000 And we should also add a success message as a flash message. 94 00:06:03,000 --> 00:06:08,650 So we'll do that again, redirectAttributes.addFlashAttribute, 95 00:06:08,650 --> 00:06:09,760 we called it flash. 96 00:06:10,810 --> 00:06:17,260 New FlashMessage and we'll say Category deleted. 97 00:06:19,040 --> 00:06:21,870 And that'll have a status of SUCCESS. 98 00:06:26,000 --> 00:06:28,660 Add my semicolon there, wonderful. 99 00:06:28,660 --> 00:06:34,940 And now let's redirect the browser back to the /URI instead of returning null here. 100 00:06:34,940 --> 00:06:39,380 So I'll redirect to /categories. 101 00:06:39,380 --> 00:06:40,530 Now let's check our service and 102 00:06:40,530 --> 00:06:44,160 Dao implementations to see what is left to code. 103 00:06:44,160 --> 00:06:47,190 So let me open categoryServiceImpl. 104 00:06:48,415 --> 00:06:53,840 CategoryServiceImpl, now it looks like our delete method isn't finished yet, 105 00:06:53,840 --> 00:06:57,360 so let's call upon the Dao to do its work here. 106 00:06:57,360 --> 00:07:00,020 So we have an override categoryDao. 107 00:07:00,020 --> 00:07:02,710 So let's grab that and 108 00:07:02,710 --> 00:07:06,910 call it delete method, passing the object we receive as a parameter value. 109 00:07:07,910 --> 00:07:10,410 Now before we head to the categorDaoImp, 110 00:07:10,410 --> 00:07:12,910 one word about this service implementation. 111 00:07:12,910 --> 00:07:16,130 Back in the controller we check for non-empty categories. 112 00:07:16,130 --> 00:07:20,010 There's plenty of reason to do this in the service layer instead of the controller 113 00:07:20,010 --> 00:07:20,940 like we did. 114 00:07:20,940 --> 00:07:23,070 And I encourage you to experiment with this. 115 00:07:23,070 --> 00:07:28,990 For example, you might check for non-empty categories here in this method. 116 00:07:28,990 --> 00:07:32,700 And if you find one, throw a category not empty exception, 117 00:07:32,700 --> 00:07:35,050 which would be a short class that you would write. 118 00:07:35,050 --> 00:07:36,480 Then, in your controller, 119 00:07:36,480 --> 00:07:39,280 you could surround your service call with a try catch. 120 00:07:39,280 --> 00:07:42,085 With this set up, you wouldn't have to duplicate the check for 121 00:07:42,085 --> 00:07:49,700 non-empty categories if you want to reuse this service to power say an API as well. 122 00:07:49,700 --> 00:07:53,050 Check the teachers notes for more information on that approach. 123 00:07:53,050 --> 00:07:56,230 Okay, let's switch now to the Dao implementation. 124 00:07:56,230 --> 00:08:00,870 So in the dao package, I will open CategoryDaoImpl and 125 00:08:00,870 --> 00:08:04,080 let's focus on this delete method here. 126 00:08:04,080 --> 00:08:06,840 In here we'll stick our normal transactional code. 127 00:08:06,840 --> 00:08:09,054 That is we'll open the hibernate session. 128 00:08:13,402 --> 00:08:18,789 We'll begin a transaction, we'll delete the category. 129 00:08:23,152 --> 00:08:29,813 We'll commit the transactions, so session.getTransaction().commit(). 130 00:08:29,813 --> 00:08:31,870 And then we'll close the session. 131 00:08:33,990 --> 00:08:38,350 Okay, with code written all the way from the UI to the persistence or 132 00:08:38,350 --> 00:08:41,530 dao layer, it's time to test this feature. 133 00:08:41,530 --> 00:08:45,690 Let's restart the app, I'm gonna open my panel here. 134 00:08:45,690 --> 00:08:48,720 I'm gonna kill the previous instance of this application and 135 00:08:48,720 --> 00:08:50,167 rerun that bootRun task. 136 00:08:53,150 --> 00:08:56,220 Looks like everything compiled successfully and 137 00:08:56,220 --> 00:08:57,950 let's see if we start successfully. 138 00:08:57,950 --> 00:08:59,650 Cool, let's switch to Chrome now. 139 00:09:01,440 --> 00:09:05,400 Now when Chrome, I want to navigate to a category without any GIFs and 140 00:09:05,400 --> 00:09:06,296 click delete. 141 00:09:06,296 --> 00:09:09,677 So I'm gonna go to categories and let's go to Validation, 142 00:09:09,677 --> 00:09:12,130 I don't think I have any GIFs in here. 143 00:09:12,130 --> 00:09:15,000 And I'm going to click Delete. 144 00:09:15,000 --> 00:09:17,770 Now, low and behold, we get an error. 145 00:09:17,770 --> 00:09:21,290 Now check out this message here, it says, 146 00:09:21,290 --> 00:09:27,050 failed to lazily initialize a collection of GIFs. 147 00:09:28,140 --> 00:09:29,750 Keep that message in mind and 148 00:09:29,750 --> 00:09:33,780 let's flip back to IntelliJ to see if we can see what happened. 149 00:09:36,770 --> 00:09:40,960 In IntelliJ, if I scroll up to the point in my console 150 00:09:40,960 --> 00:09:44,280 to where the exception was first displayed. 151 00:09:45,340 --> 00:09:48,540 I find that the culprit in my source code 152 00:09:48,540 --> 00:09:52,440 is located in the CategoryController on line 130. 153 00:09:52,440 --> 00:09:54,850 I'm going to click on that and 154 00:09:54,850 --> 00:10:00,140 this is the line that is acting as the culprit in this case. 155 00:10:00,140 --> 00:10:03,590 Now what that error message was saying is that a collection 156 00:10:03,590 --> 00:10:08,240 in this category entity right here was not initialized. 157 00:10:08,240 --> 00:10:09,430 What collection is that? 158 00:10:09,430 --> 00:10:14,390 Well, it looks like this collection right here, let me show you what's happening. 159 00:10:14,390 --> 00:10:16,770 I'm gonna open the Category entity. 160 00:10:17,960 --> 00:10:22,690 In this Category class, we have a mapped collection named gifs. 161 00:10:23,960 --> 00:10:27,330 Now, this collection by default is configured to 162 00:10:27,330 --> 00:10:31,880 not be initialized when a Category entity is fetched from the database. 163 00:10:31,880 --> 00:10:34,060 This is called lazy loading. 164 00:10:34,060 --> 00:10:37,790 It turns out that in this OneToMany annotation here, 165 00:10:37,790 --> 00:10:43,040 there is another element that you can specify here called fetch. 166 00:10:43,040 --> 00:10:48,660 Now the default value of the fetch element is indeed fetchtype.lazy. 167 00:10:48,660 --> 00:10:52,140 Now you can change this to fetchtype.eager, 168 00:10:52,140 --> 00:10:56,840 which means that every time one or more categories are fetched from the database. 169 00:10:56,840 --> 00:10:59,460 Another query will be performed to fetch all GIF 170 00:10:59,460 --> 00:11:03,490 entities from the database that are associated with that category. 171 00:11:03,490 --> 00:11:06,648 Now, a word of caution one eager fetching. 172 00:11:06,648 --> 00:11:12,415 If Gif lib has many categories and each category has many Gifs, 173 00:11:12,415 --> 00:11:17,380 then getting a list of all categories would involve executing more queries. 174 00:11:17,380 --> 00:11:20,900 To fetch all the Gifs associated with every category, 175 00:11:20,900 --> 00:11:24,500 which we aren't even using in the category index page. 176 00:11:24,500 --> 00:11:26,840 This would be a huge waste of resources and 177 00:11:26,840 --> 00:11:30,950 would make our users wait a long time for request to process. 178 00:11:30,950 --> 00:11:32,890 Fortunately, there's a better option for 179 00:11:32,890 --> 00:11:37,460 our scenario, we can simply initialize the collection when we need to. 180 00:11:37,460 --> 00:11:40,400 Let's go back to the CategoryController to remind ourselves 181 00:11:40,400 --> 00:11:43,750 of where this Category entity came from to begin with. 182 00:11:43,750 --> 00:11:48,480 So in the CategoryController, we got this category object 183 00:11:48,480 --> 00:11:53,410 from a service call to the findbyId method. 184 00:11:53,410 --> 00:11:57,580 The way our application is coded right now, it makes enough sense to fetch 185 00:11:57,580 --> 00:12:01,880 all the GIFs for a category, only when a single category is being fetched. 186 00:12:01,880 --> 00:12:07,870 So all the way down in our Dao layer, we can tweak the findById method as follows. 187 00:12:07,870 --> 00:12:09,720 Let me go to the CategoryDaoImpl. 188 00:12:11,320 --> 00:12:17,280 If I scroll to the findById method, here is where we'll need to make those changes. 189 00:12:17,280 --> 00:12:19,310 Before I close the session, 190 00:12:19,310 --> 00:12:25,185 I'm going to call Hibernate.initialize on that collection of GIFs. 191 00:12:25,185 --> 00:12:30,880 Category.getGifs. 192 00:12:30,880 --> 00:12:34,800 What this will do is it will make sure that that Gifs collection 193 00:12:34,800 --> 00:12:38,420 is initialized before this session is closed. 194 00:12:38,420 --> 00:12:43,660 We were seeing that error because we tried to access a mapped collection 195 00:12:43,660 --> 00:12:49,310 outside of a hibernate session, which just isn't possible. 196 00:12:49,310 --> 00:12:52,750 So with that in place, let's test this again. 197 00:12:52,750 --> 00:12:57,787 I'm going to kill this app and I'm going to rerun that bootRun task. 198 00:13:05,245 --> 00:13:07,805 And it looks like our applications started successfully, so 199 00:13:07,805 --> 00:13:10,255 let's go back to the browser. 200 00:13:10,255 --> 00:13:14,355 I am just going to hit the back button to go back to that edit form and 201 00:13:14,355 --> 00:13:18,000 hit Delete and see what happens this time. 202 00:13:18,000 --> 00:13:21,090 Hey, look at that category was indeed deleted. 203 00:13:21,090 --> 00:13:24,220 Now if you recall the name in that category was Validation and 204 00:13:24,220 --> 00:13:26,530 I no longer see that category here. 205 00:13:26,530 --> 00:13:27,510 And in addition, 206 00:13:27,510 --> 00:13:33,160 I see this nice flash message indicating that the delete successfully worked. 207 00:13:34,280 --> 00:13:37,970 Now of course, we'd wanna test this with the non-empty category as well, so 208 00:13:37,970 --> 00:13:40,020 let's find one to do that. 209 00:13:40,020 --> 00:13:44,810 I believe we had one in Technology, so let's delete that one. 210 00:13:45,950 --> 00:13:47,060 And there we have it, 211 00:13:47,060 --> 00:13:52,030 that flash message indicating that only empty categories can be deleted. 212 00:13:52,030 --> 00:13:57,510 So that Technology category is still here if I go back to the list of categories, 213 00:13:57,510 --> 00:14:00,020 we have not lost that Technology category. 214 00:14:00,020 --> 00:14:05,530 And we have successfully prevented the deletion of categories that contain Gifs. 215 00:14:07,060 --> 00:14:10,300 And there you go, deleting categories, done. 216 00:14:10,300 --> 00:14:13,740 It's time for us to move onto deleting the Gifs themselves, prepare for 217 00:14:13,740 --> 00:14:14,720 another coding task.