EP03 – Spring Data and Why it rocks (Part 2)

spring data logoIn the previous episode we talked about the first two letters in CRUD.

So naturally that means that next up is the “U” in “CRUD”, we need to be able to update our existing methods.

So, believe it or not, if you were playing around with the code that we wrote in the section above, you may have noticed that updating an existing record seems to work already.

And you’re absolutely right… it does work.

So, your job is done!

Shall we move on?

Not yet.

Let me first explain why it works…

You see, when you used Spring Data to execute a findOne (or findAll) it will grab the id of the entity. We then put the entity on the model and passed it to our view.

The view then passed that same Entity back to our server on POST, and therefore the id field will still be populated.

When the id of an Entity has a value, Hibernate knows that that Entity must exist in the database, and therefore when you execute a save you probably want to update the existing record (and not add a new one).

So you see, it’s actually Hibernate doing the heavy lifting here. Because that id is already populated with a value, Hibernate will update the record with all the data you passed in.

Sweet deal!

Now let’s talk “D”, the “Delete” statement.

Let’s start with an easy example… our example.

When deleting a row from the database that contains a simple object (like the FoodDiary object) then it’s as simple as invoking the delete method on our repository and passing in the object we wish to delete.

Let’s code up a delete method, shall we? (We’ll get into the more complex scenarios afterwards)

@RequestMapping(value="food-diaries/{foodDiaryId}", method=RequestMethod.POST, params="action=delete")
public String deleteFoodDiary(@PathVariable Long foodDiaryId) 
{
  // we always need to load our entity from the database before we perform a delete
  FoodDiary foodDiaryItem = foodDiaryRepository.findOne(foodDiaryId);

  foodDiaryRepository.delete(foodDiaryItem);

  return "foodDiary";
}

Pretty straight-forward stuff. Be sure to load your entity first, then pass the loaded entity into your delete method and you’re all set.

But what about the more complex scenario that I mentioned? What is this scenario?

It’s when you have relationships in your objects.

I’ve spoken about relationships before.

Let’s add a relationship into our FoodDiary object, I want to add a relationship in such a way that the FoodDiary entity is the child in the relationship (as this will create the difficult deletion scenario).

Let’s assume that we also have User entities in our code… it doesn’t have to be fancy to work:

@Entity
public class User
{
  @Id @GeneratedValue
  private Long id;
  private String username;
  private String password;
  @OneToMany(mappedBy="user")
  private Set<FoodDiary> foodDiaries = new HashSet<>();

  // getters and setters
}

And we’ll also need to update our FoodDiary Entity to reflect a bidirectional OneToMany relationship:

@Entity
public class FoodDiary
{
  @Id
  @GeneratedValue
  private Long id;
  private String food;
  private String mealType;
  private Time timeOfDay;
  private Double cost;
  @ManyToOne
  private User user;
  // getters and setters
}

Voila, we’ve created a relationship between a User and a FoodDiary (where one User has access to many FoodDiary objects).

Now, we can talk about our deleting dilemma!

What happens when we want to delete a FoodDiary Entity now? If you give our current delete method a try, you’ll notice that it runs through the code just fine, but hibernate doesn’t actually execute the delete anymore.

What the heck?

The reason why it doesn’t delete the FoodDiary entry is because it’s a child entry to a parent (with a bidirectional relationship).

In our Java object scenario here, when we invoked the delete, Hibernate looks and sees that we’re trying to delete the child object, but the child object still exists in the context of the parent (and the child still points to the parent).

So that means there’s more work to do here. Let’s modify our delete method given our new scenario.

@RequestMapping(value="food-diaries/{foodDiaryId}", method=RequestMethod.POST, params="action=delete")  
public String deleteFoodDiary(@PathVariable Long foodDiaryId)   
{  
  // we always need to load our entity from the database before we perform a delete  
  FoodDiary foodDiaryItem = foodDiaryRepository.findOne(foodDiaryId);  
     
  // Get the User entity that owns this FoodDiary entity  
  User user = foodDiaryItem.getUser();  

  if (user != null)
  {
    user.getFoodDiaries().remove(foodDiaryItem);  
  }
    
  // now we need to remove the user from our foodDiaryItem;  
  foodDiaryItem.setUser(null);  
    
  foodDiaryRepository.delete(foodDiaryItem);  
    
  return "redirect:/";  
}  

Now, when we execute our delete statement, Hibernate sees that this FoodDiary item has been removed from the parent User object, and this FoodDiary item also doesn’t reference the parent anymore… so it will execute the appropriate delete statement and remove the desired FoodDiary row from our database.

Phew!

That’s a fair bit of extra work just to do a delete… but Hibernate doesn’t want to take chances “guessing” at what exactly you want to delete, so you need to be explicit with it.

Pro Tip: If you enable cascades (in the @OneToMany mapping), performing a delete of a parent object (the User in this case) will delete the parent and all the children without having to do any extra coding… you just execute a delete and pass in the parent entity that you loaded with a findOne() call.

Download my Code

For all of you “keeners” out there, I’ve created a GitHub repository for this project.

Here’s a link to the files so you can check them out.

Or if you prefer, here’s the clone URL so you can download the whole thing: https://github.com/tp02ga/FoodDiary.git