2

I have been developing blog application using Spring MVC + Spring Data JPA + Hibernate. Now I have a problem with optimistic locking mechanism. I will share a piece of code below. I have added @Version annotation to entity. Than I am using two different browser to update same record at same time. Every time I perform the save action it increases the versioon +1 and updates data. But doesnt throw any exception As you know , expected exception is like OptirmisticException. I have searched but no information about it yet. If you could clarify me, I will be appricate. Here is a piece of code from controller. Thanks All.

@RequestMapping(value="/edit/{id}", method = RequestMethod.POST) 
public String postEdit(@PathVariable Long id , @ModelAttribute("category")Category formCategory){

    try {
        Category category = categoryService.findOneCategory(id);
        if(category!=null) {
            category.setCatName(formCategory.getCatName());
            categoryService.save(category);
        }
    } catch (Exception e) {
          LOGGER.debug("Error message  : "+e);
    } 

    return PAGE_DEFAULT;
}
Danielson
  • 2,605
  • 2
  • 28
  • 51
Mesut Dogan
  • 572
  • 7
  • 16
  • 1
    And why should it? You are retrieving a fresh copy from the database before updating. So you will always have the most recent version. If you want to use optimistic locking store the one that you already have. – M. Deinum Aug 14 '15 at 06:51
  • I know, I am retrieving fresh data everytime, but there is no update method and if I try to save the data (comes from form) , it automatically adds a new data instead of updating it. Any solution for this ? – Mesut Dogan Aug 14 '15 at 06:54
  • You need to store the `Category` in the session, update values and then save that instance. You are using a `@ModelAttribtue` so adding a `@SessionAttributes("category")` should do the trick. Include a method argument of type `SessionStatus` and after saving call `setComplete()` on it. This will clean any session attributes not needed anymore. – M. Deinum Aug 14 '15 at 06:58
  • Or, rather than polluting the session with data that shouldn't be in the session, make sure the form also posts the version of the category (the one it had when displaying the form), and copy the posted version to the persistent category. – JB Nizet Aug 14 '15 at 07:17

3 Answers3

2

Applying optimistic locking within Spring MVC is a multi-step process.

Step 1: When displaying the form for the user to edit, store the version # of the entity to be edited. This can either be stored as a hidden field in the form or within the session (to minimize the size of your session, you can just store the version number and not the whole entity). You also need to store the primary key of the entity.

Step 2: When saving the entity (your postEdit step), you first need to load it from the database using the stored primary key. The easiest way to do this I believe is to use a @ModelAttribute method.

Step 3: In your postEdit method, obtain the stored version and compare it to the version of the loaded entity. If they differ, report an error to the user. I prefer to indicate a validation error rather than throw an exception, since it results in a cleaner user experience.

Some code examples that are based on storing the version using a hidden field:

@ModelAttribute("entity")
public Entity getEntity(@RequestParam(value="id", required=false) Long id) {
    if (id == null) {
        return new Entity();
    } else {
        Optional<Entity> entity = entityRepo.findById(id);
        // TODO: Throw exception if not found (probably concurrently deleted).
        return entity.orElse(null);
    }
}

@RequestMapping(value="/entity/save", method=RequestMethod.POST)
public String postEdit(@Valid @ModelAttribute("entity") Entity entity, BindingResult bindingResult, @RequestParam(value = "version", required=false) Long version) {

    if (entity.getVersion() != version) {
        bindingResult.reject("concurrency", "The data was modified concurrently.");
    }

    if (bindingResult.hasErrors()) {
        // TODO: Redisplay edit form
    } else {
        // TODO: Proceed with saving.
    }
}

Note that if you are using a hidden field named "version" and the property of your entity is also named "version" then you should NOT have a setVersion() method on your entity, as otherwise Spring will automatically populate (bind) the version property based on the hidden field rendering your check useless.

1

To do it correctly, you need to store the Entity in session between requests.

You could store the version in a hidden form field, but session is more secure, in that a user cannot change the value.

Don't reload it from the database in your POST handler.

Add @SessionAttributes("category") above your controller.

See Spring MVC: Validation, Post-Redirect-Get, Partial Updates, Optimistic Concurrency, Field Security

Community
  • 1
  • 1
Neil McGuigan
  • 46,580
  • 12
  • 123
  • 152
0

As I know, a common way is to send the version column to the client as a hidden field or something, and get that posted back. Then, in the post method, compare that to the freshly retrieved version from the database. Throw an exception if they don't match.

Refer to Spring Lemon for a detailed example.

Response to comment:

See this post for details. In summary, JPA does check for the version column, but it won't allow you to set it. Hence, we are left with no option but to check it manually.

Community
  • 1
  • 1
Sanjay
  • 8,755
  • 7
  • 46
  • 62
  • But If I do it manually what is the meaning of optmistic locking mechanism of jpa. Sincerely , If there is such a mechanism , It should control all behavours and exceptions.Otherwise I could manually write a version field and a function that could insert version to db and everytime increases it . – Mesut Dogan Aug 14 '15 at 07:40
  • You can perfectly set the version by yourself. Hibernate will do the check for you. Doing the check manually is useless (since Hibernate does it for you), and wouldn't detect the case where another transaction updates the row between the time the entity is loaded and the time it's saved in the database. – JB Nizet Aug 14 '15 at 10:44
  • @JB: I had tried it repeatedly, but setting the version indeed didn't work. The stackoverflow post linked from my answer has more details. – Sanjay Aug 14 '15 at 10:49