How to update only the child and not the parent in a bi-directional relationship

Viewed 168

I have a query that I would like to only update the child, but it ends up updating both parent and child hence eating a lot of memory on the server, been trying to fix this for a while a with no luck

the parent

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

the child

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

my controller

public void handleCatalogReport(String catalogPayload) {
  log.info("Catalog report event data received: \n" + catalogPayload);
  try {
    ErpCatalogList catalogItems = getMapper().readValue(catalogPayload, ErpCatalogList.class);
    log.info("Catalog report event data received: \n" + catalogPayload);
    if (ObjectUtils.isEmpty(catalogItems.getData())) {
      log.info("Catalog list received could not be mapped, or is empty: {}", catalogItems);
    } else {
      var savedRecords = itemPriceService.processCatalogPayload(catalogItems);
      log.info("Saved catalog records: {}", savedRecords);
    }
  } catch (JsonProcessingException e) {
    e.printStackTrace();
  }
}

my function to update the prices

public ResponseWrapper processCatalogPayload(ErpCatalogList catalogItems) {
  List<ItemPriceEntity> updateItemPrices = new ArrayList<>();
  catalogItems.getData().forEach(territoryData -> {
    territoryData.getData().forEach(catalogItem -> {
      // Check if item exists. If so update
      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 itemm 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();
}

the sample SQL Logs

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=?

the Challenge am getting here is that whenever data is sent, the product_bundle_code says it is getting updated but it is not, also the item_price gets updated at the same time causing a spike on the resources, while I only need the item_price to get updated alone, hibernate shows it is updating the product_bundle_code, am not sure what am doing wrong, Any help is appreciated

1 Answers

My guess is, that the JsonType you are using in ProductBundleEntity is not capable to determine whether a field is dirty, so it assumes that ProductBundleEntity is always dirty. You might have to debug into the following code to see what is happening exactly: https://github.com/vladmihalcea/hibernate-types/blob/master/hibernate-types-55/src/main/java/com/vladmihalcea/hibernate/type/json/internal/JsonTypeDescriptor.java#L75

It could also very well be that your equals implementation in ProductBundleItem is wrong and due to that Hibernate assumes the entity is dirty.

Since ItemPriceEntity#productBundle uses eager fetching (the default of @ManyToOne), the ProductBundleEntity is loaded into the first level context and before transaction commit, the changes are flushed. Due to it being considered dirty, you are seeing this behavior.

Related