你能帮我了解如何有效地将实体插入多个表吗?
我有3个表和3个实体。Pricebook具有一系列SKU,每个SKU具有1个价格。基本上,我要以事务方式插入多个实体,如果有约束,我必须更新链中的实体。
一旦尝试向数据库中并行插入多个Pricebook,就出现了问题,因此我实际上正在捕获PostgreSQL死锁。我发现一种解决方法是将它们逐个插入队列中,但是我知道这不是一个好主意。
这可能是一个愚蠢的问题,之前已经有人回答过,但是我希望有人能给我提示。
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "pricebook")
public class Pricebook {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
//....
}
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "sku")
public class Sku {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
//....
}
@Entity
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "price")
public class Price {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
@JoinColumn(name = "pricebook_id", referencedColumnName = "id", unique = true)
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Pricebook pricebook;
@JoinColumn(name = "sku_id", referencedColumnName = "id", unique = true)
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Sku sku;
//....
}
这是Upbook的PricebookService逻辑。
@NonNull
@Transactional
public Pricebook createPricebook(@NonNull CreatePricebookRequest request) {
final Instant startDate = PricebookConverter.toDate(request.getStartDate());
final Instant expirationDate = PricebookConverter.toDate(request.getExpirationDate());
if (startDate.isAfter(expirationDate)) {
throw new InvalidParametersException("The pricebook's start date later then its expiration date.");
}
final Region region = regionService.findRegionByName(request.getRegion());
final Optional<Pricebook> isPricebookFound =
pricebookRepository.findByRegionAndPricebookTypeAndStartDateAndExpirationDate(region,
request.getPricebookName(), startDate, expirationDate);
final Pricebook savedOrUpdatedPricebook;
if (isPricebookFound.isPresent()) {
final Pricebook foundPricebook = isPricebookFound.get();
savedOrUpdatedPricebook = pricebookRepository.save(
new Pricebook(foundPricebook.getPricebookId(), request.getName(), foundPricebook.getPricebookName(), foundPricebook.getRegion(), foundPricebook.getStartDate(),
foundPricebook.getExpirationDate());
logger.info("pricebook is updated successfully, pricebook={}", savedOrUpdatedPricebook);
} else {
savedOrUpdatedPricebook = pricebookRepository.save(
new Pricebook(request.getName(), request.getPricebookType(), region, startDate, expirationDate);
logger.info("pricebook is created successfully, pricebook={}", savedOrUpdatedPricebook);
}
final List<Sku> skus = skuService.createSku(savedOrUpdatedPricebook, request.getSkus());
logger.debug("skus are saved successfully, skus={}", skus);
return savedOrUpdatedPricebook;
}
这是upsert的SkuService逻辑。skuToCreateOrUpdate
基本上只是一种方法,它可以将逻辑(如果被发现)或新逻辑包装起来并返回一个新对象。
@NonNull
public List<Sku> createSku(@NonNull Pricebook pricebook, @NonNull List<CreateSkuRequest> skus) {
return skus.stream().map(sku -> {
final Optional<Sku> foundSku = skuRepository.findByCode(sku.getCode());
final Sku savedOrUpdatedSku = skuRepository.save(skuToCreateOrUpdate(sku, foundSku.map(Sku::getSkuId).orElse(null)));
final List<Price> prices = priceService.createPrices(pricebook, savedOrUpdatedSku, sku.getPrice());
logger.debug("prices are saved successfully, prices={}", prices);
return savedOrUpdatedSku;
}).collect(toList());
}
这是Upsert的PriceService逻辑。
@NonNull
public List<Price> createPrices(@NonNull Pricebook pricebook, @NonNull Sku sku, @NonNull CreatePriceRequest price) {
final Optional<Price> foundPrice = priceRepository.findByPricebookAndSku(pricebook, sku);
final Price savedOrUpdatedPrice;
if (foundPrice.isPresent()) {
final Price priceToUpdate = foundPrice.get();
savedOrUpdatedPrice = priceRepository.save(
new Price(priceToUpdate.getPriceId(),
pricebook,
sku);
logger.info("price is updated successfully, price={}", savedOrUpdatedPrice);
} else {
savedOrUpdatedPrice = priceRepository.save(
new Price(pricebook, sku);
logger.info("price is created successfully, price={}", savedOrUpdatedPrice);
}
return Collections.singletonList(savedOrUpdatedPrice);
}
我到处都在使用JpaRepository。像这样
@Repository
public interface PricebookRepository extends JpaRepository<Pricebook, Long> {}
@Repository
public interface SkuRepository extends JpaRepository<Sku, Long> {}
@Repository
public interface PriceRepository extends JpaRepository<Price, Long> {}
我相信你可能会遇到此问题,尤其是在两个事务都尝试插入相同的SKU时,这一问题很有可能发生。
如果是这样,我可以考虑两种缓解方法:
部分解决方案:尝试排序的SKU中List<CreateSkuRequest> skus
通过sku.code
使用和(如果这还不够)saveAndFlush()
来存储它们,确保插入的顺序。这应该消除循环等待,这意味着现在至少有一个事务应该成功(另一个事务可能会违反唯一约束)
完整的解决方案:如果你希望两个事务都成功,则必须为该SKU
表获取一个表级锁。你应该可以使用自定义更新查询来执行此操作:
@Query(value = "LOCK TABLE SKU IN EXCLUSIVE MODE", nativeQuery = true)
@Modifying
void lockTable();
然后,只需将该方法作为内部的第一个操作调用即可createSku
。请注意,这可能仅比将事务放入队列稍有效率,因此,如果我是你,我可能仍会采用这种方法。
编辑我也不太了解为你提供两次事务冲突的一致结果的确切方案,这是你要并行化的批量插入类型的东西吗?如果你真的想并行运行事务,则可以对输入集进行分组,以使SKU不会重叠。或者,将重复数据删除并插入Sku
s中。就像我说的,我不知道用例是什么,所以不确定这是否有意义。
谢谢,我将尝试您的解决方案并返回结果
似乎您是对的,锁定比在队列中执行锁定更有效。谢谢你,对我有帮助