0

I have a controller and i'm validating ModelAttribute through a validator before updating the entity like the code below. The validator receives the object to be updated in the obj variable.

The problem i have is that the modified object (obj) in validate method is saved to database when i run this method restaurantRepository.findByEmail. It has something to do with @Transactional. I don't understand why it saves the obj, I haven't passed it onto the method.

I have read this SO post Why does @Transactional save automatically to database The answer says that

Before the transactional method is about to return, the transaction commits, meaning all changes to the managed entities are flushed to the database.

But i understand that the managed entities are the entities existing inside the scope of @Transactional. I haven't passed obj into the method so i don't understand why it's saving automatically.

Restaurant

@Entity
@Table(name="restaurant")
@SequenceGenerator(name="restaurant_seq", sequenceName="restaurant_seq")
public class Restaurant{

    private String name;

    private String email;

    private String phonenumber;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhonenumber() {
        return phonenumber;
    }

    public void setPhonenumber(String phonenumber) {
        this.phonenumber = phonenumber;
    }

}

RestaurantContoller

@RequestMapping("/restaurant")
@Controller
public class RestaurantController {

    @Autowired
    private RestaurantService restaurantRepository;

    @Autowired
    private RestaurantValidator restaurantValidator;

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.setValidator(restaurantValidator);
    }

    @GetMapping("/{id}")
    public String viewRestaurant(@PathVariable("id") Long restaurant_id, final ModelMap model) {
        Restaurant restaurant =  restaurantService.findById(restaurant_id);
        if(!model.containsAttribute("restaurantModel")){
            model.addAttribute("restaurantModel", restaurant ); 
        }
        return "pages/restaurant_view_new";
    }

    @PatchMapping("/{restaurantModel}")
    public String updateRestaurant(@Valid @ModelAttribute("restaurantModel") Restaurant editRestaurant, BindingResult bindingResult, final ModelMap model, RedirectAttributes redirectAttributes) {
        if (bindingResult.hasErrors()) {
redirectAttributes.addFlashAttribute("org.springframework.validation.BindingResult.restaurantModel", bindingResult);
            redirectAttributes.addFlashAttribute("restaurantModel", editRestaurant);
            redirectAttributes.addFlashAttribute("saveErrors", true);

            return "redirect:/restaurant/" + editRestaurant.getId();
        }
        restaurantRepository.save(editRestaurant);
        redirectAttributes.addFlashAttribute("saveSuccess", true);
        return "redirect:/restaurant/" + editRestaurant.getId();
    }
}

RestaurantValidator

@Component
public class RestaurantValidator implements Validator {

        @Autowired
        RestaurantRepository restaurantRepository;

        public boolean supports(Class clazz) {
            return Restaurant.class.equals(clazz);
        }

        public void validate(Object obj, Errors e) {
            ValidationUtils.rejectIfEmpty(e, "name", "form.required");
            ValidationUtils.rejectIfEmpty(e, "email", "form.required");
            ValidationUtils.rejectIfEmpty(e, "phonenumber", "form.required");
            Restaurant p = (Restaurant) obj;
            if(restaurantRepository.findByEmail(p.getEmail()).size() > 0 ){
                e.rejectValue("email", "form.email.duplicate");
            }
        }
}

RestaurantRepository

@Transactional
public interface RestaurantRepository extends JpaRepository<Restaurant, Long>{
    List<Restaurant> findByEmail(String email);
}
nissim_dev
  • 323
  • 2
  • 14

2 Answers2

0

In my experience, when you call a SELECT-statement that involves querying the database table of which you have modified entities in the persistence context, this triggers a flush.

I think the reason for this is that Hibernate says "you are doing a select on a table which might include the unflushed entity I have here, and then I might be conflicted on which one of the two is the most current".

I do not know whether this has to do with a specific FLUSH strategy though.

Kirinya
  • 245
  • 3
  • 12
  • I see. There's another case in which if I try to find another entity (which has a ManytoOne relation with the modified entity), it will still save the modified entity to the database at the end of the call which seems uncalled for. This only applies if @Transactional is present – nissim_dev Mar 07 '18 at 09:07
0

The question is missing some details, but my assumption is that obj is in Persistent state.
See more details here.

So any modifications are synchronized automatically with the database while committing the transaction.

If you don't whant the modifications to sync you need to detach the object before modifying it.
See

EntityManager.detach(Object var)
EntityManager.persits(Object var)

Following your last comment I suggest reading the following as well Why is Hibernate Open Session in View considered a bad practice?

Haim Raman
  • 11,508
  • 6
  • 44
  • 70
  • Okay, I think that the object is in persistent state. But any ways I'm not sending that object to the the repository class when i call restaurantRepository.findByEmail(p.getEmail()). Are you saying that any object in persistent state is saved when any kind of transactional method is run? even if they aren't related – nissim_dev Mar 07 '18 at 12:48
  • @user2049132 if you make changes to a persistent (not detached or new) object, those changes will be reflected in the database. It doesn't matter what methods you call or what you do. If you have a non-detached entity `A` and you call `A.setFoo(bar)`, then that change will make it to the database. – Kayaman Mar 07 '18 at 12:58
  • okay. I'm doing somethig like `ARepository.findByFoo('foo')` , hell even `Brepository.findbyBoo("boo")` And both saves the object X (instance of A) which is persistent.(persistent because spring automagically merges changes in HTTP request to persistent object in DB) in the code i posted above. More reference to the magic [here](https://stackoverflow.com/questions/31768289/spring-data-jpa-how-to-update-a-model-elegantly) – nissim_dev Mar 07 '18 at 13:19
  • Spring is not the main player here, it's the JPA provider and all objects pulled buy the query are in the persist state for the session. Your session is opened from your view (see link) this is why you get this behavoir – Haim Raman Mar 07 '18 at 14:00