【问题标题】:Why does @Transactional save entity outside scope of transactional to db?为什么@Transactional 将事务范围之外的实体保存到数据库?
【发布时间】:2018-08-15 05:31:24
【问题描述】:

我有一个控制器,我正在通过验证器验证 ModelAttribute,然后像下面的代码一样更新实体。验证器在 obj 变量中接收要更新的对象。

我遇到的问题是,当我运行这个方法 restaurantRepository.findByEmail 时,验证方法中的修改对象(obj)被保存到数据库中。它与@Transactional 有关。我不明白为什么它会保存 obj,我还没有将它传递给方法。

我已阅读此 SO 帖子 Why does @Transactional save automatically to database 答案是这样的

在事务方法即将返回之前,事务提交,这意味着对托管实体的所有更改都会刷新到数据库中。

但我知道托管实体是存在于@Transactional 范围内的实体。我没有将 obj 传递给方法,所以我不明白为什么它会自动保存。

餐厅

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

}

餐厅管理员

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

餐厅验证器

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

【问题讨论】:

  • 我在您发布的内容中没有看到任何修改 obj 的代码。
  • 抱歉。在控制器的 POST 请求中收到时,obj 已被修改。修改成html表单提交给控制器。
  • 代码很重要。发布它。
  • @JBNizet 我已经添加了控制器代码。当它到达 PatchMapping 方法的第一行时,对象已经被保存。我可以验证这一点,因为我可以看到在休眠日志上运行“UPDATE”语句。
  • 请贴出实体的代码

标签: java spring hibernate jpa transactional


【解决方案1】:

根据我的经验,当您调用涉及查询数据库表的 SELECT 语句时,您已在持久性上下文中修改了实体,这会触发刷新。

我认为这样做的原因是 Hibernate 说“您正在对可能包括我在这里的未刷新实体的表进行选择,然后我可能会在两者中哪一个是最新的方面发生冲突”。

我不知道这是否与特定的 FLUSH 策略有关。

【讨论】:

  • 我明白了。在另一种情况下,如果我尝试查找另一个实体(与修改后的实体具有 ManytoOne 关系),它仍会在调用结束时将修改后的实体保存到数据库中,这似乎是不必要的。这仅适用于存在 @Transactional 的情况
【解决方案2】:

这个问题缺少一些细节,但我的假设是 obj 处于 Persistent 状态。
查看更多详情here

因此,在提交事务时,任何修改都会自动与数据库同步。

如果您不希望同步修改,则需要在修改之前分离对象。

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

在您最后的评论之后,我建议您也阅读以下内容Why is Hibernate Open Session in View considered a bad practice?

【讨论】:

  • 好的,我认为该对象处于持久状态。但是当我调用 restaurantRepository.findByEmail(p.getEmail()) 时,我不会将该对象发送到存储库类。你是说当任何类型的事务方法运行时,任何处于持久状态的对象都会被保存?即使他们不相关
  • @user2049132 如果您对持久(非分离或新)对象进行更改,这些更改将反映在数据库中。你调用什么方法或做什么并不重要。如果您有一个非分离实体A 并且您调用A.setFoo(bar),那么该更改将进入数据库。
  • 好的。我正在做类似ARepository.findByFoo('foo') 之类的事情,甚至是Brepository.findbyBoo("boo") 并且两者都保存了持久的对象X(A的实例)。(持久,因为spring自动将HTTP请求中的更改合并到DB中的持久对象)在代码中贴在上面。更多魔法参考here
  • Spring 不是这里的主要参与者,它是 JPA 提供者,所有被拉到查询中的对象都处于会话的持久状态。您的会话是从您的视图中打开的(请参阅链接)这就是您得到这种行为的原因
猜你喜欢
  • 2014-02-28
  • 2012-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-23
  • 1970-01-01
  • 1970-01-01
  • 2015-01-06
相关资源
最近更新 更多