如何阻止Hibernate更新子实体时触发父实体更新?
问题描述
仅需更新子实体ItemPriceEntity,但Hibernate会先执行父实体ProductBundleEntity的更新语句,再更新子实体,尽管已在子实体的@ManyToOne关联上设置了updatable=false。
相关代码
父实体ProductBundleEntity
@Entity @Table(name = "product_bundles") @EqualsAndHashCode(onlyExplicitlyIncluded = true) @TypeDefs({@TypeDef(name = "json", typeClass = JsonType.class)}) public class ProductBundleEntity extends BaseEntity { @JsonIgnore @OneToMany(mappedBy = "productBundle") private List<ItemPriceEntity> itemPrices; }
子实体ItemPriceEntity
@Entity @Table(name = "item_prices") @Data @EqualsAndHashCode(onlyExplicitlyIncluded = true) @TypeDefs({@TypeDef(name = "json", typeClass = JsonType.class)}) public class ItemPriceEntity extends BaseEntity { @ManyToOne @JoinColumn( name = "item_code", referencedColumnName = "product_bundle_code", insertable = false, updatable = false) private ProductBundleEntity productBundle; }
更新逻辑
public ResponseWrapper processCatalogPayload(ErpCatalogList catalogItems) { List<ItemPriceEntity> updateItemPrices = new ArrayList<>(); catalogItems.getData().forEach(territoryData -> { territoryData.getData().forEach(catalogItem -> { var itemPrice = itemPriceRepository.findByItemCodeAndTerritory(catalogItem.getItemCode(), catalogItem.getTerritory()); if (itemPrice.isPresent()) { itemPrice.get().setStock(catalogItem.getActualQty()); itemPrice.get().setProjectedStock((catalogItem.getProjectedQty() == null)? 0 : catalogItem.getProjectedQty()); itemPrice.get().setLastModifiedDate(Instant.now()); updateItemPrices.add(itemPrice.get()); } }); }); if (!updateItemPrices.isEmpty()) { var updatedItems = itemPriceRepository.saveAll(updateItemPrices); log.info("Updated item prices: {} ", updatedItems); return ResponseWrapper.builder() .code(HttpStatus.OK.value()) .message("Item prices updated successfully") .build(); } return ResponseWrapper.builder() .code(HttpStatus.NOT_FOUND.value()) .message("No Item prices found to update") .build(); }
SQL日志片段
Hibernate: select productbun0_.id as id1_10_0_, productbun0_.active as active2_10_0_, productbun0_.creation_date as creation3_10_0_, productbun0_.last_modified_date as last_mod4_10_0_, productbun0_.display_item_group as display_5_10_0_, productbun0_.image as image6_10_0_, productbun0_.items as items7_10_0_, productbun0_.price_list_set as price_li8_10_0_, productbun0_.product_bundle_code as product_9_10_0_, productbun0_.product_bundle_description as product10_10_0_, productbun0_.product_bundle_group as product11_10_0_, productbun0_.promo_bundle as promo_b12_10_0_ from product_bundles productbun0_ where productbun0_.product_bundle_code=? Hibernate: update product_bundles set active=?, last_modified_date=?, display_item_group=?, image=?, items=?, price_list_set=?, product_bundle_code=?, product_bundle_description=?, product_bundle_group=?, promo_bundle=? where id=? Hibernate: update item_prices set active=?, last_modified_date=?, currency=?, discount_price=?, display_name=?, item_code=?, product_bundle_group=?, projected_stock=?, selling_price=?, stock=?, territory=?, territory_status=?, uom=?, warehouse=? where id=?
原因分析
Hibernate的脏检查机制是核心问题:
- 默认情况下
@ManyToOne是EAGER(立即加载),查询ItemPriceEntity时会自动加载关联的ProductBundleEntity实例,该实例进入Hibernate Session的持久化上下文。 - Hibernate会在事务flush时(比如
saveAll执行时)检查所有持久化状态的实体,若检测到属性变化(比如BaseEntity中的last_modified_date被自动更新、实体属性被意外修改),就会生成更新语句。 - 你设置的
updatable=false仅控制子实体是否能修改关联字段(item_code),不影响父实体本身的脏检查逻辑。
解决方案
1. 将父实体关联改为懒加载
修改子实体的@ManyToOne注解,设置fetch = FetchType.LAZY,避免查询子实体时自动加载父实体,从根源上避免父实体进入持久化上下文:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn( name = "item_code", referencedColumnName = "product_bundle_code", insertable = false, updatable = false) private ProductBundleEntity productBundle;
2. 优化查询,仅加载子实体数据
修改itemPriceRepository.findByItemCodeAndTerritory方法,使用投影查询或DTO查询,只获取ItemPriceEntity的必要字段,不关联加载父实体:
// 示例:使用Spring Data JPA的投影 public interface ItemPriceProjection { Long getId(); Integer getStock(); Integer getProjectedStock(); // 其他需要更新的字段 } @Repository public interface ItemPriceRepository extends JpaRepository<ItemPriceEntity, Long> { Optional<ItemPriceProjection> findByItemCodeAndTerritory(String itemCode, String territory); }
之后在更新逻辑中,将投影转换为实体或直接使用实体进行更新(若使用投影,需额外查询实体但确保不加载父实体)。
3. 分离父实体(若必须加载)
如果业务逻辑需要加载父实体,可在更新子实体前将父实体从Session中分离,阻止Hibernate跟踪其状态:
// 在获取itemPrice后执行 if (itemPrice.isPresent()) { ItemPriceEntity entity = itemPrice.get(); // 分离父实体 entityManager.detach(entity.getProductBundle()); // 执行更新操作 entity.setStock(catalogItem.getActualQty()); // ...其他更新 updateItemPrices.add(entity); }
4. 检查BaseEntity的自动更新逻辑
确认BaseEntity中是否存在@PreUpdate或类似注解自动更新last_modified_date等字段:
// 若BaseEntity有如下逻辑,会导致父实体被标记为脏数据 @PreUpdate public void preUpdate() { this.lastModifiedDate = Instant.now(); }
如果父实体不需要自动更新该字段,可移除该注解,或针对父实体单独处理。
内容的提问来源于stack exchange,提问作者arriff
相关产品推荐
相关产品推荐

