Hibernate Postgresql select for update 存在外连接问题

问题描述 投票:0回答:4

我在尝试使用 Spring 数据、Hibernate 作为 JPA 实现和 Postgresql 选择更新行时遇到了问题。

假设我们有实体:A,B,C。

public class A{
   @Id
   private Long id;

   @OneToMany(fetch = FetchType.EAGER)
   private Set<B> bSet;

   @OneToMany(fetch = FetchType.EAGER)
   private Set<C> cSet;
}

假设我们要选择 A 以及所有相关的 B 和 C 实体进行更新,即锁定与 A 表相关的行。

@Query(SELECT a FROM A a 
       LEFT JOIN FETCH a.bSet
       LEFT JOIN FETCH a.cSet
       WHERE a.id=?)
@Lock(LockModeType.PESSIMISTIC_WRITE)
public A selectAndLockA(Long Aid);

查询将如下所示

SELECT a.column1, ... from tableA a LEFT JOIN tableB b ... FOR UPDATE of a,c

更新 a,c

查询将尝试锁定两个表,这会导致异常,例如: org.postgresql.util.PSQLException:错误:FOR UPDATE 无法应用于外连接的可为空一侧

我尝试归档的是仅锁定第一个表“FOR UPDATE OF a”

是否可以以某种方式配置或告诉 Hibernate 仅锁定第一个表。

postgresql hibernate jpa spring-data
4个回答
12
投票

PostreSQL 不支持此功能。如果您执行外部 SELECT,则没有什么可以阻止某人将行插入到 LEFT JOINED 表中,从而修改您正在查看的结果集(例如,重复读取时列将不再为 NULL)。

详细说明请参阅这里


4
投票

问题提出已经很长时间了,但我也有类似的问题,希望我的回答能帮助别人。

假设我们有这个 JPA 实体:

@Entity
@Table(name = "card_transactions")
public class CardTransactionsEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "card_trans_seq")
    @SequenceGenerator(name = "card_trans_seq", sequenceName = "card_trans_seq")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumns({
            @JoinColumn(name = "ofd_id", referencedColumnName = "ofd_id"),
            @JoinColumn(name = "receipt_id", referencedColumnName = "receipt_id")})
    private ReceiptsEntity receipt;

    @Column
    @Enumerated(EnumType.STRING)
    private CardTransactionStatus requestStatus;

    ...

}
@Entity
@Table(name = "receipts")
public class ReceiptsEntity {
    @EmbeddedId
    private OfdReceiptId id; 

    ...
}

@Embeddable
public class OfdReceiptId implements Serializable {

    @Column(name = "ofd_id")
    @Enumerated(EnumType.STRING)
    private OfdId ofdId;

    @Column(name = "receipt_id")
    private String receiptId;

    ...
}

我们希望选择带有获取的 ReceiptsEntity 的 CardTransactionsEntity,以便仅对 CardTransactionsEntity 进行悲观更新。这可以使用 Hibernate 和 Spring Data JPA 存储库来完成

public interface CardTransactionRepository extends JpaRepository<CardTransactionsEntity, Long> {

    @Query("select ct from CardTransactionsEntity ct left join fetch ct.receipt r where ct.requestStatus = :requestStatus")
    @Lock(value = LockModeType.PESSIMISTIC_WRITE)
    @QueryHints(value = {
            @QueryHint(name = "javax.persistence.lock.timeout", value = "-2"), // LockOptions.SKIP_LOCKED
            @QueryHint(name = "org.hibernate.lockMode.r", value = "NONE") // "r" is alias for ct.receipt and will excluded from PESSIMISTIC_WRITE
    })
    List<CardTransactionsEntity> loadCardTransactions(@Param("requestStatus") CardTransactionStatus requestStatus, Pageable pageable);

}

这个存储库方法将执行类似的查询

SELECT ct.*, r.* from card_transactions ct LEFT OUTER JOIN receipts r ON ct.ofd_id = r.ofd_id and ct.receipt_id = r.receipt_id WHERE ct.request_status=? LIMIT ? FOR UPDATE OF ct SKIP LOCKED

0
投票

您可以通过使用 FetchType.LAZY 连接表来绕过此错误。此获取类型是默认类型,不需要为 @OneToMany 连接指定。

public class A{
   @Id
   private Long id;

   @OneToMany
   private Set<B> bSet;

   @OneToMany
   private Set<C> cSet;
}

0
投票

老问题,但我的用例的描述可能对某人有帮助。

我在同一个查询中连接了很多表+@EntityGraph来急切地获取一些东西。

因此,我们可以通过仅提供 @QueryHints 来指定锁定的确切表(而不是整个层次结构):

//@Lock(LockModeType.PESSIMISTIC_WRITE) is inappropriate because it locks the whole hierarchy.
    @QueryHints(value = {
            @QueryHint(name = "org.hibernate.lockMode.ms", value = "PESSIMISTIC_WRITE") // "ms" is alias for MeasurementSet and will only be used in PESSIMISTIC_WRITE
    })

但即使在这种情况下,Hibernate 中也存在一个错误。来自 HQL 的别名受到来自 @EntityGraph 的隐式别名的干扰。

解决方案: 摆脱@EntityGraph并仅通过HQL获取所有内容。

@QueryHints(value = {
            @QueryHint(name = "org.hibernate.lockMode.ms", value = "PESSIMISTIC_WRITE") // "ms" is alias for MeasurementSet and will only be used in PESSIMISTIC_WRITE
    })
    @Query("SELECT ms from MeasurementSet ms "
            + "INNER JOIN ms.measurementSetGroup msg "
            + "LEFT JOIN FETCH ms.pomSections ps LEFT JOIN FETCH ps.pointsOfMeasure pom LEFT JOIN FETCH pom.pomMeasurements "
            + "LEFT JOIN FETCH ms.prototypeMilestones "
            + "WHERE ms.id = :id and msg.modelNumber = :modelNumber")
    Optional<MeasurementSet> findEagerlyByIdAndModelNumberWithLock(Long id, String modelNumber);
最新问题
© www.soinside.com 2019 - 2024. All rights reserved.