我在尝试使用 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 仅锁定第一个表。
PostreSQL 不支持此功能。如果您执行外部 SELECT,则没有什么可以阻止某人将行插入到 LEFT JOINED 表中,从而修改您正在查看的结果集(例如,重复读取时列将不再为 NULL)。
详细说明请参阅这里
问题提出已经很长时间了,但我也有类似的问题,希望我的回答能帮助别人。
假设我们有这个 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
您可以通过使用 FetchType.LAZY 连接表来绕过此错误。此获取类型是默认类型,不需要为 @OneToMany 连接指定。
public class A{
@Id
private Long id;
@OneToMany
private Set<B> bSet;
@OneToMany
private Set<C> cSet;
}
老问题,但我的用例的描述可能对某人有帮助。
我在同一个查询中连接了很多表+@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);