Sql Spring数据jpa插入到多个表中,以避免锁定表

Sql Spring数据jpa插入到多个表中,以避免锁定表,sql,postgresql,spring-boot,hibernate,jpa,Sql,Postgresql,Spring Boot,Hibernate,Jpa,您能帮助我理解如何有效地将实体插入多个表中吗 我有3个表和3个实体。Pricebook有一个SKU数组,每个SKU有一个价格。基本上,我想要的是以事务方式插入多个实体,在约束的情况下,我必须更新链中的实体 当我尝试并行地将多个Pricebook插入DB时,问题就出现了,所以我实际上遇到了PostgreSQL死锁。我发现一个解决办法是将它们逐个插入队列,但我知道这不是一个好主意 这可能是一个愚蠢的问题,以前已经回答过了,但我希望有人能给我一个提示 @实体 @诺尔格构装师 @AllArgsCons

您能帮助我理解如何有效地将实体插入多个表中吗

我有3个表和3个实体。Pricebook有一个SKU数组,每个SKU有一个价格。基本上,我想要的是以事务方式插入多个实体,在约束的情况下,我必须更新链中的实体

当我尝试并行地将多个Pricebook插入DB时,问题就出现了,所以我实际上遇到了PostgreSQL死锁。我发现一个解决办法是将它们逐个插入队列,但我知道这不是一个好主意

这可能是一个愚蠢的问题,以前已经回答过了,但我希望有人能给我一个提示

@实体
@诺尔格构装师
@AllArgsConstructor
@表(name=“pricebook”)
公共类价格手册{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
私人长id;
//....
}
@实体
@诺尔格构装师
@AllArgsConstructor
@表(name=“sku”)
公共类Sku{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
私人长id;
//....
}
@实体
@诺尔格构装师
@AllArgsConstructor
@表(name=“price”)
公共类价格{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
私人长id;
@JoinColumn(name=“pricebook\u id”,referencedColumnName=“id”,unique=true)
@manytone(fetch=FetchType.LAZY,cascade=CascadeType.ALL)
私人价格手册;
@JoinColumn(name=“sku\u id”,referencedColumnName=“id”,unique=true)
@manytone(fetch=FetchType.LAZY,cascade=CascadeType.ALL)
私人库存单位;
//....
}
这是一个upsert的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;
    }
    @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);
    }
下面是upsert的价格服务逻辑

  @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;
    }
    @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);
    }
@NonNull
公共列表createPrices(@NonNull Pricebook Pricebook、@NonNull Sku Sku、@NonNull CreatePriceRequest price){
最终可选foundPrice=priceRepository.findBypriceBook和sku(pricebook,sku);
最终价格保存或更新价格;
if(foundPrice.isPresent()){
最终价格priceToUpdate=foundPrice.get();
savedOrUpdatedPrice=priceRepository.save(
新价格(priceToUpdate.getPriceId(),
价格手册,
sku);
info(“价格已成功更新,价格={}”,SavedUpdatedPrice);
}否则{
savedOrUpdatedPrice=priceRepository.save(
新价格(价格手册、sku);
info(“成功创建价格,价格={}”,savedOrUpdatedPrice);
}
返回集合。singletonList(SavedUpdatedPrice);
}
我到处都在用JPA存储库,就像这样

@Repository
public interface PricebookRepository extends JpaRepository<Pricebook, Long> {}

@Repository
public interface SkuRepository extends JpaRepository<Sku, Long> {}

@Repository
public interface PriceRepository extends JpaRepository<Price, Long> {}
@存储库
公共接口PricebookRepository扩展了JpaRepository{}
@存储库
公共接口存储库扩展了JpaRepository{}
@存储库
公共接口PriceRepository扩展了JpaRepository{}

我相信您可能会面临这样的问题,尤其是当两个交易都试图插入相同的SKU时

如果是这样的话,我可以想出两种方法来缓解它:

  • 部分解决方案:尝试按
    sku.code
    列表sku
    中的sku进行排序,并(如果这还不够)使用
    saveAndFlush()
    存储它们,确保插入顺序。这将消除循环等待,意味着现在至少一个事务应该成功(另一个可能会遇到唯一的约束冲突)

  • 完整解决方案:如果您希望两个事务都成功,则必须为
    SKU
    表获取表级锁。您应该能够使用自定义更新查询执行此操作:

  • 然后,只需调用该方法作为
    createSku
    中的第一个操作。请注意,这可能会比将事务放入队列稍微更有效,因此如果我是你,我可能仍然会使用该方法


    编辑我也不太了解两个事务冲突的一致结果的确切情况,这是一种批量插入类型的东西,你正在尝试并行化吗?如果你真的想并行运行事务,也许你可以对输入集进行分组,这样SKU就不会重叠。或者,消除重复数据nd预先插入
    Sku
    s。正如我所说,我不知道用例是什么,所以不确定这是否有意义。

    是否有错误消息告诉您为什么会出现死锁?按照我的理解,如果您在两个事务中运行相同的实体创建操作,则不应该出现死锁,因为inse的顺序是rtion将是相同的,因此没有循环等待。请发布服务方法,或者启用SQL日志记录,以查看哪些语句会死锁是的,当然,错误消息看起来很短。如下所示:
    error:deadlock detected Detail:进程27988等待事务1996705435的共享锁;被进程27990阻止。进程s 27990等待事务1996705089上的ShareLock;被进程27988阻止。提示:有关查询详细信息,请参阅服务器日志。其中:插入与“sku”相关的索引元组(153,8)时;嵌套异常是org.postgresql.util.PSQLException:ERROR:deadlock detected
    您是否也可以发布用于插入
    Pricebook
    的代码?您提到了一些关于升级和约束的内容,所以我认为存在一些条件逻辑?我也理解
    sku
    更像是一个查找表,对吗两个事务中的es共享任何
    sku
    s?此外,它将有助于查看每个事务执行的实际查询,因为这样我们就可以推断正在获取哪些锁