简体   繁体   English

为什么@Transactional将实体保存在事务范围之外的数据库?

[英]Why does @Transactional save entity outside scope of transactional to db?

I have a controller and i'm validating ModelAttribute through a validator before updating the entity like the code below. 我有一个控制器,并且在更新实体(如以下代码)之前,我正在通过验证器验证ModelAttribute。 The validator receives the object to be updated in the obj variable. 验证器在obj变量中接收要更新的对象。

The problem i have is that the modified object (obj) in validate method is saved to database when i run this method restaurantRepository.findByEmail. 我的问题是,当我运行restaurantRepository.findByEmail方法时,validate方法中的修改对象(obj)将保存到数据库中。 It has something to do with @Transactional. 它与@Transactional有关。 I don't understand why it saves the obj, I haven't passed it onto the method. 我不明白为什么它保存obj,还没有将其传递给方法。

I have read this SO post Why does @Transactional save automatically to database The answer says that 我已经读了这篇SO帖子, 为什么@Transactional自动保存到数据库中答案说,

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. 但我知道托管实体是@Transactional范围内存在的实体。 I haven't passed obj into the method so i don't understand why it's saving automatically. 我没有将obj传递给该方法,所以我不明白为什么它会自动保存。

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 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 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);
}

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. 以我的经验,当您调用涉及查询在持久性上下文中修改了实体的数据库表的SELECT语句时,将触发刷新。

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". 我认为这样做的原因是Hibernate说:“您正在对一个表进行选择,该表可能包括我在这里未摆放的实体,然后我可能会冲突这两者中哪个是最新的”。

I do not know whether this has to do with a specific FLUSH strategy though. 我不知道这是否与特定的FLUSH策略有关。

The question is missing some details, but my assumption is that obj is in Persistent state. 这个问题缺少一些细节,但是我的假设是obj处于Persistent状态。
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? 在您发表最后一条评论之后,我还建议您阅读以下内容: 为什么View中的Hibernate Open Session被认为是不好的做法?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM