如何在spring data jpa查询中指定@lock超时?

Posted

技术标签:

【中文标题】如何在spring data jpa查询中指定@lock超时?【英文标题】:How to specify @lock timeout in spring data jpa query? 【发布时间】:2015-06-28 05:51:11 【问题描述】:

如何指定@Lock查询超时? 我用的是Oracle 11g,希望能用'select id from table where id = ?1 for update wait 5'之类的。

我这样定义方法:

@Lock(LockModeType.PESSIMISTIC_WRITE)
Stock findById(String id);

它似乎永远锁定。 当我在LocalContainerEntityManagerFactoryBean.jpaProperties中设置javax.persistence.lock.timeout=0时,没有效果。

【问题讨论】:

【参考方案1】:

要悲观地锁定实体,请将锁定模式设置为 PESSIMISTIC_READPESSIMISTIC_WRITEPESSIMISTIC_FORCE_INCREMENT.

如果不能获得悲观锁,但是锁失败 不会导致事务回滚,LockTimeoutException 是 扔了。

Pessimistic Locking Timeouts

持久性提供者应该以毫秒为单位的时间长度 等待获取数据库表的锁可以使用指定 javax.persistence.lock.timeout 属性。如果需要的时间 获得一个锁超过​​这个属性的值,一个 LockTimeoutException 会被抛出,但是当前事务 不会被标记为回滚。 如果此属性设置为 0,则 持久性提供者应该抛出一个LockTimeoutException,如果它 无法立即获得锁。

如果在多个位置设置了javax.persistence.lock.timeout,则 值将按以下顺序确定:

    EntityManagerQuery methods 之一的参数。 @NamedQuery 注释中的设置。 Persistence.createEntityManagerFactory 方法的参数。 persistence.xml 部署描述符中的值。

对于 Spring Data 1.6 或更高版本

@Lock 从 Spring Data JPA 1.6 版开始支持 CRUD 方法(事实上,已经有一个 milestone 可用)。有关详细信息,请参阅此ticket。

使用该版本,您只需声明以下内容:

interface WidgetRepository extends Repository<Widget, Long> 

  @Lock(LockModeType.PESSIMISTIC_WRITE)
  Widget findOne(Long id);

这将导致后备存储库代理的 CRUD 实现部分将配置的 LockModeType 应用于EntityManager 上的find(…) 调用。

另一方面,

对于之前版本的 Spring Data 1.6

Spring Data 悲观 @Lock 注释仅适用于(如您所指出的)查询。我知道没有注释会影响整个事务。您可以创建一个findByOnePessimistic 方法,该方法使用悲观锁调用findByOne,也可以更改findByOne 以始终获得悲观锁。

如果您想实现自己的解决方案,您可能可以。在后台,@Lock 注释由 LockModePopulatingMethodIntercceptor 处理,它执行以下操作:

TransactionSynchronizationManager.bindResource(method, lockMode == null ? NULL : lockMode);

您可以创建一些具有ThreadLocal&lt;LockMode&gt; 成员变量的静态锁管理器,然后在每个存储库中的每个方法周围都有一个方面,该方法调用bindResource,并在ThreadLocal 中设置锁定模式。这将允许您在每个线程的基础上设置锁定模式。然后,您可以创建自己的 @MethodLockMode 注释,它将方法包装在一个方面,该方面在运行该方法之前设置特定于线程的锁定模式,并在运行该方法后将其清除。

资源链接:

    How to enable LockModeType.PESSIMISTIC_WRITE when looking up entities with Spring Data JPA? How to add custom method to Spring Data JPA Spring Data Pessimistic Lock timeout with Postgres JPA Query API

悲观锁超时的各种例子

设置悲观锁

一个实体对象可以通过lock方法显式锁定:

em.lock(employee, LockModeType.PESSIMISTIC_WRITE);

第一个参数是一个实体对象。第二个参数是请求的锁定模式。

如果在调用锁时没有活动事务,则会抛出TransactionRequiredException,因为显式锁定需要活动事务。

如果请求的悲观锁不能被授予,则抛出LockTimeoutException

如果另一个用户(即 由另一个 EntityManager 实例表示)当前持有一个 PESSIMISTIC_WRITE 锁定该数据库对象。 如果当前有其他用户,PESSIMISTIC_WRITE 锁定请求将失败 持有PESSIMISTIC_WRITE 锁定或PESSIMISTIC_READ 锁定 该数据库对象。

设置查询提示(范围)

可以在以下范围(从全局到本地)设置查询提示:

对于整个持久性单元 - 使用 persistence.xml 属性:

<properties>
   <property name="javax.persistence.query.timeout" value="3000"/>
</properties>

对于 EntityManagerFactory - 使用 createEntityManagerFacotory 方法:

Map<String,Object> properties = new HashMap();
properties.put("javax.persistence.query.timeout", 4000);
EntityManagerFactory emf =
  Persistence.createEntityManagerFactory("pu", properties);

对于 EntityManager - 使用 createEntityManager 方法:

Map<String,Object> properties = new HashMap();
properties.put("javax.persistence.query.timeout", 5000);
EntityManager em = emf.createEntityManager(properties);

或使用 setProperty 方法:

em.setProperty("javax.persistence.query.timeout", 6000);

对于 named query 定义 - 使用 hints 元素:

@NamedQuery(name="Country.findAll", query="SELECT c FROM Country c",
    hints=@QueryHint(name="javax.persistence.query.timeout", value="7000"))

对于特定的查询执行 - 使用 setHint 方法(在查询执行之前):

query.setHint("javax.persistence.query.timeout", 8000);

资源链接:

    Locking in JPA Pessimistic Lock Timeout

【讨论】:

我是 spring 新手,只有在 applicationContext.xml 中定义了 entitymanagerfactory。当我在每个查询上使用 @QueryHint 注释定义超时时,超时效果很好,但我想在项目的所有查询上配置它而不注释每个查询。如何将其作为属性添加到 xml 中的实体管理器工厂?【参考方案2】:

您可以在 Spring Data 中使用@QueryHints

@Lock(LockModeType.PESSIMISTIC_WRITE)
@QueryHints(@QueryHint(name = "javax.persistence.lock.timeout", value ="5000"))
Stock findById(String id)

【讨论】:

我正在尝试这个。第一个事务能够获取锁并在释放锁之前进行一些处理。同时,当第二个事务到来并尝试获取锁时,它似乎并没有等待 value 属性中定义的时间(在您的示例中为 5000 ms),而是立即抛出异常“无法提取 ResultSet;SQL [ n/a];嵌套异常是 org.hibernate.exception.LockAcquisitionException: could not extract ResultSet" @old_soul_on_the_run 我遇到了与您评论完全相同的问题,您找到解决方案了吗?【参考方案3】:

对于Spring Data 1.6或更高版本,我们可以使用spring data jpa提供的@Lock注解。

此外,还可以使用@QueryHints 设置锁定超时。最初在默认 CRUD 方法中不支持查询提示注释,但在修复 1.6M1 后可用。 https://jira.spring.io/browse/DATAJPA-173

下面是一个带有 PESSIMISTIC_WRITE 模式类型的悲观锁的示例,它是排他锁。

@Lock(LockModeType.PESSIMISTIC_WRITE) 
@QueryHints(@QueryHint(name = "javax.persistence.lock.timeout", value   ="5000")) 
Customer findByCustomerId(Long customerId);

【讨论】:

【参考方案4】:

javax.persistence.lock.timeout 似乎对我也不起作用,如下所示:

@QueryHints(@QueryHint(name = "javax.persistence.lock.timeout",value = "15000"))

但后来我尝试了其他有效的方法。现在我使用实体管理器配置我的 hbernate,而不是使用 @Repository 和 CrudRepository。使用 createQuery 以及锁定和设置锁定超时。并且此配置按预期工作。 我有两个并行运行的事务,并试图在数据库中锁定完全相同的行。第一个事务能够获得 WRITE 锁并在释放锁之前持有锁大约 10 秒。同时,第二个事务尝试在同一行获取锁,但由于 javax.persistence.lock.timeout 设置为 15 秒,它等待锁被释放然后获取自己的锁。因此使流程序列化。

@Component
public class Repository 

    @PersistenceContext
    private EntityManager em;

    public Optional<Cache> getById(int id)
        List<Cache> list = em.createQuery("select c from Cache c where c.id = ?1")
                            .setParameter(1, id)
                            .setHint("javax.persistence.lock.timeout", 15000)
                            .setLockMode(LockModeType.PESSIMISTIC_WRITE)
                            .getResultList();

    
        return Optional.ofNullable(list.get(0));
    
    
    public void save(Cache cache) 
        cache = em.find(Cache.class, cache.getId());
        em.merge(cache);
    

【讨论】:

我们可以将 javax.persistence.lock.timeout 的值设置为 300 毫秒还是应该始终为 1000 秒的倍数? 锁定超时不一定是 1000 的倍数。

以上是关于如何在spring data jpa查询中指定@lock超时?的主要内容,如果未能解决你的问题,请参考以下文章

如何在spring data mongoDB中指定数据库名称

如何使用 spring 的 jdbcTemplate 在 SQL 查询中指定参数

Spring Data JPA @Modifying 是不是有任何用途而不指定其属性?

学习Spring-Data-Jpa---定义方法查询

当我使用两个表实体时如何在 JPA @JoinColumn 中指定忽略大小写

Spring Data 系列学习Spring Data JPA 自定义查询,分页,排序,条件查询