Hibernate PostgreSQL select for update with outer join issue

u0njafvf  于 12个月前  发布在  PostgreSQL
关注(0)|答案(4)|浏览(143)

我遇到了一个问题,试图选择更新行使用Spring Data 与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:ERROR:FOR UPDATE不能应用于外部连接的可空端
我尝试归档的是锁定第一个表“FOR UPDATE OF A”
有没有可能以某种方式配置或告诉Hibernate只锁定第一个表。

hgqdbh6s

hgqdbh6s1#

PostreSQL不支持这一点。如果你做了一个外部SELECT,没有什么可以阻止某人插入一行到LEFT JOINED表中,从而修改你正在查看的结果集(例如,在重复读取时,列将不再是NULL)。
有关详细说明,请参见here

zwghvu4y

zwghvu4y2#

这是一个很长一段时间以来的问题,但我有一个类似的问题,希望我的答案将帮助别人。
假设我们有这个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;

    ...

}

个字符
我们希望选择CardTransactionsEntity with fetched ReceivtsEntity用于仅悲观更新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

kyks70gy

kyks70gy3#

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

public class A{
   @Id
   private Long id;

   @OneToMany
   private Set<B> bSet;

   @OneToMany
   private Set<C> cSet;
}

字符串

62lalag4

62lalag44#

老问题,但我的用例描述可能对某人有帮助。
我在同一个查询中有很多表的连接+ @ passiotyGraph来获取一些东西。但只需要在一个主表中PESIMISTIC_passiotyGraph。
早期提出的从锁中排除表的解决方案很麻烦(大量的排除),在@ MyrtyGraph的情况下还有未知的别名。
因此,我们可以通过只提供@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的别名会被来自@ MyrtyGraph的隐式别名干扰。
解决方案:摆脱@ MyrtyGraph,只通过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);

相关问题