休眠事实:了解刷新操作顺序很重要

Hibernate将开发人员的思维方式从思考SQL转变为思考对象状态转换。 根据Hibernate Docs,实体可能处于以下状态之一:

  • new / transient:实体不与持久性上下文关联,因为它是数据库不知道的新创建的对象。
  • 持久性:实体与持久性上下文相关联(位于第一级缓存中),并且存在一个表示该实体的数据库行。
  • 分离:实体以前与持久性上下文相关联,但是持久性上下文已关闭,或者手动撤消了该实体。
  • 已删除:实体被标记为已删除,并且持久性上下文将在刷新时从数据库中将其删除。

通过调用EntityManager方法来将对象从一种状态移动到另一种状态,例如:

  • 坚持()
  • 合并()
  • 去掉()

级联允许将给定事件从父级传播到子级,还简化了实体关系管理的管理。

在刷新期间,Hibernate会将当前持久性上下文记录的更改转换为SQL查询。

现在,考虑一下以下代码中发生了什么(为简洁起见,将其简化):

@Entity
public class Product {

   @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true)
   @OrderBy("index")
   private Set images = new LinkedHashSet();

   public Set getImages() {
      return images;
   }

   public void addImage(Image image) {
      images.add(image);
      image.setProduct(this);
   }

   public void removeImage(Image image) {
      images.remove(image);
      image.setProduct(null);
   }
}

@Entity
public class Image {

   @Column(unique = true)
   private int index;

   @ManyToOne
   private Product product;

   public int getIndex() {
      return index;
   }

   public void setIndex(int index) {
      this.index = index;
   }

   public Product getProduct() {
      return product;
   }

   public void setProduct(Product product) {
      this.product = product;
   }
}

final Long productId = transactionTemplate.execute(new TransactionCallback() {
   @Override
   public Long doInTransaction(TransactionStatus transactionStatus) {
      Product product = new Product();

      Image frontImage = new Image();
      frontImage.setIndex(0);

      Image sideImage = new Image();
      sideImage.setIndex(1);

      product.addImage(frontImage);
      product.addImage(sideImage);

      entityManager.persist(product);
      return product.getId();
   }
});

try {
   transactionTemplate.execute(new TransactionCallback() {
      @Override
         public Void doInTransaction(TransactionStatus transactionStatus) {
         Product product = entityManager.find(Product.class, productId);
         assertEquals(2, product.getImages().size());
         Iterator imageIterator = product.getImages().iterator();

         Image frontImage = imageIterator.next();
         assertEquals(0, frontImage.getIndex());
         Image sideImage = imageIterator.next();
         assertEquals(1, sideImage.getIndex());

         Image backImage = new Image();
         sideImage.setName("back image");
         sideImage.setIndex(1);

         product.removeImage(sideImage);
         product.addImage(backImage);

         entityManager.flush();
         return null;
     }
});
   fail("Expected ConstraintViolationException");
} catch (PersistenceException expected) {
   assertEquals(ConstraintViolationException.class, expected.getCause().getClass());
}

由于存在Image.index唯一约束,因此在刷新期间会收到ConstraintviolationException。

您可能想知道为什么会发生这种情况,因为在添加具有相同索引的backImage之前,我们为sideImage调用remove,并且答案是冲洗操作顺序。

根据Hibernate JavaDocs ,SQL操作顺序为:

  • 插入
  • 更新
  • 集合元素的删除
  • 集合元素的插入
  • 删除

因为我们的图像集合是“ mappedBy”,所以图像将控制关联,因此“ backImage”插入发生在“ sideImage”删除之前。

select product0_.id as id1_5_0_, product0_.name as name2_5_0_ from Product product0_ where product0_.id=?
select images0_.product_id as product_4_5_1_, images0_.id as id1_1_1_, images0_.id as id1_1_0_, images0_.index as index2_1_0_, images0_.name as name3_1_0_, images0_.product_id as product_4_1_0_ from Image images0_ where images0_.product_id=? order by images0_.index
insert into Image (id, index, name, product_id) values (default, ?, ?, ?)
ERROR: integrity constraint violation: unique constraint or index violation; UK_OQBG3YIU5I1E17SL0FEAWT8PE table: IMAGE

要解决此问题,您必须在除去操作之后手动刷新持久性上下文:

transactionTemplate.execute(new TransactionCallback<Void>() {
   @Override
   public Void doInTransaction(TransactionStatus transactionStatus) {
      Product product = entityManager.find(Product.class, productId);
      assertEquals(2, product.getImages().size());
      Iterator<Image> imageIterator = product.getImages().iterator();

      Image frontImage = imageIterator.next();
      assertEquals(0, frontImage.getIndex());
      Image sideImage = imageIterator.next();
      assertEquals(1, sideImage.getIndex());

      Image backImage = new Image();
      backImage.setIndex(1);

      product.removeImage(sideImage);
      entityManager.flush();

      product.addImage(backImage);

      entityManager.flush();
      return null;
   }
});

这将输出所需的行为:

select versions0_.image_id as image_id3_1_1_, versions0_.id as id1_8_1_, versions0_.id as id1_8_0_, versions0_.image_id as image_id3_8_0_, versions0_.type as type2_8_0_ from Version versions0_ where versions0_.image_id=? order by versions0_.type
delete from Image where id=?
insert into Image (id, index, name, product_id) values (default, ?, ?, ?)


翻译自: https://www.javacodegeeks.com/2013/11/hibernate-facts-knowing-flush-operations-order-matters.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值