Hibernate PostgreSQL select for update with outer join issue

u0njafvf  于 2024-01-07  发布在  PostgreSQL
关注(0)|答案(4)|浏览(174)

我遇到了一个问题,试图选择更新行使用Spring Data 与Hibernate作为JPA实现和Postgresql。
假设我们有实体A,B,C.

  1. public class A{
  2. @Id
  3. private Long id;
  4. @OneToMany(fetch = FetchType.EAGER)
  5. private Set<B> bSet;
  6. @OneToMany(fetch = FetchType.EAGER)
  7. private Set<C> cSet;
  8. }

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

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


查询将类似于

  1. 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实体:

  1. @Entity
  2. @Table(name = "card_transactions")
  3. public class CardTransactionsEntity {
  4. @Id
  5. @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "card_trans_seq")
  6. @SequenceGenerator(name = "card_trans_seq", sequenceName = "card_trans_seq")
  7. private Long id;
  8. @ManyToOne(fetch = FetchType.LAZY)
  9. @JoinColumns({
  10. @JoinColumn(name = "ofd_id", referencedColumnName = "ofd_id"),
  11. @JoinColumn(name = "receipt_id", referencedColumnName = "receipt_id")})
  12. private ReceiptsEntity receipt;
  13. @Column
  14. @Enumerated(EnumType.STRING)
  15. private CardTransactionStatus requestStatus;
  16. ...
  17. }

个字符
我们希望选择CardTransactionsEntity with fetched ReceivtsEntity用于仅悲观更新CardTransactionsEntity。这可以使用Hibernate和Spring Data JPA存储库来完成,

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


此存储库方法将执行查询,如

  1. 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连接指定。

  1. public class A{
  2. @Id
  3. private Long id;
  4. @OneToMany
  5. private Set<B> bSet;
  6. @OneToMany
  7. private Set<C> cSet;
  8. }

字符串

62lalag4

62lalag44#

老问题,但我的用例描述可能对某人有帮助。
我在同一个查询中有很多表的连接+ @ passiotyGraph来获取一些东西。但只需要在一个主表中PESIMISTIC_passiotyGraph。
早期提出的从锁中排除表的解决方案很麻烦(大量的排除),在@ MyrtyGraph的情况下还有未知的别名。
因此,我们可以通过只提供@QueryHints来指定锁的确切表(而不是整个层次结构):

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

字符串
但即使在这种情况下,Hibernate中也存在一个错误。来自HQL的别名会被来自@ MyrtyGraph的隐式别名干扰。
解决方案:摆脱@ MyrtyGraph,只通过HQL获取所有内容。

  1. @QueryHints(value = {
  2. @QueryHint(name = "org.hibernate.lockMode.ms", value = "PESSIMISTIC_WRITE") // "ms" is alias for MeasurementSet and will only be used in PESSIMISTIC_WRITE
  3. })
  4. @Query("SELECT ms from MeasurementSet ms "
  5. + "INNER JOIN ms.measurementSetGroup msg "
  6. + "LEFT JOIN FETCH ms.pomSections ps LEFT JOIN FETCH ps.pointsOfMeasure pom LEFT JOIN FETCH pom.pomMeasurements "
  7. + "LEFT JOIN FETCH ms.prototypeMilestones "
  8. + "WHERE ms.id = :id and msg.modelNumber = :modelNumber")
  9. Optional<MeasurementSet> findEagerlyByIdAndModelNumberWithLock(Long id, String modelNumber);

展开查看全部

相关问题