java - jpa-springEntitymanager.find超时不起作用

标签 java hibernate jpa spring-boot

我正在尝试使用 jpa 和 db 作为 mysql 在我的 Spring Boot 应用程序中实现悲观锁。我的目标是让存储库首先从数据库中获取一行,然后对此设置锁定。当此事务正在运行时,没有人应该能够读取同一行。以下是我实现的代码:

@Repository
@Transactional
public class UserRepo {

@PersistenceContext
private EntityManager entityManager;

/**
 *
 * @param token
 * @param data
 * @return
 */
public boolean lockUser(String token, int data) {
        Map<String, Object> props = new HashMap<String, Object>();
    props.put("javax.persistence.query.timeout", 0);
        User usr = entityManager.find(User.class, token, LockModeType.PESSIMISTIC_WRITE, props);
        System.out.println("BEFOREE LOCK = " + 
        Thread.currentThread().getId() + " user="+usr.getPlayerBalance());

        entityManager.lock(usr, LockModeType.PESSIMISTIC_WRITE, props);
        System.out.println("AFTER LOCK = " + Thread.currentThread().getId());
        if (data>2) {
            System.out.println("IN IF BEFORE SLEEP Thread = " + Thread.currentThread().getId());
            Thread.sleep(90000);
            System.out.println("IN IF AFTER SLEEP Thread = " + Thread.currentThread().getId());
        } else {
            System.out.println("IN ELSE Thread = " + Thread.currentThread().getId());
        }  return false;
      }
 }

现在,当我运行这个时,当第一个请求带有数据> 3时,该提取行然后锁定该行并且线程 hibernate 90秒。现在,当第二个请求带有 data = 1 时,线程将等待锁定(em.find-具有超时为 0 毫秒的悲观锁)。现在理想情况下它应该抛出异常,因为我已将超时设置为 0。但第二个线程不会立即抛出异常,而且线程从数据库读取行然后等待。

最佳答案

LockModeType.PESSIMISTIC_WRITE 用于锁定行,可以轻松测试。

我将 UserRepo 稍微调整为:

@Repository
public class UserRepo {

    @PersistenceContext
    private EntityManager entityManager;

    @Transactional
    public void lockUser(final Long id, final boolean wait) throws InterruptedException {
        entityManager.clear(); // be sure there is nothing in the cache, actually the threads don't share first level cache

        final Map<String, Object> props = new HashMap<String, Object>();
        props.put("javax.persistence.query.timeout", 0);

        System.out.println("Thread " + Thread.currentThread().getId() + " EXECUTES SELECT FOR UPDATE");
        entityManager.find(User.class, id, LockModeType.PESSIMISTIC_WRITE, props);

        if (wait) {
            System.out.println("Thread " + Thread.currentThread().getId() + " started blocking!");
            Thread.sleep(10000);
            System.out.println("Thread " + Thread.currentThread().getId() + " finished blocking!");
        }

        System.out.println("Thread " + Thread.currentThread().getId() + " FINISHED QUERY");
    }
}

我为该存储库创建了一个(不美观但功能齐全)测试:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;

@RunWith(SpringRunner.class)
@Transactional
@SpringBootTest
public class UserRepoTests {

    @Autowired
    private UserRepo userRepo;

    @Test
    public void testSelectForUpdate() throws InterruptedException {

        final Runnable requestOne = () -> {
            try {
                userRepo.lockUser(1L, true); // this one should wait and block the others
            } catch (InterruptedException e) {
            }
        };

        final Runnable requestTwo = () -> {
            try {
                userRepo.lockUser(1L, false);
            } catch (InterruptedException e) {
            }
        };

        final Runnable requestThree = () -> {
            try {
                userRepo.lockUser(1L, false);
            } catch (InterruptedException e) {
            }
        };

        final Thread threadOne = new Thread(requestOne);
        threadOne.start();

        Thread.sleep(1000); // give the first one some time to start

        final Thread threadTwo = new Thread(requestTwo);
        threadTwo.start();
        final Thread threadThree = new Thread(requestThree);
        threadThree.start();

        Thread.sleep(20000); // wait before destroying context
    }

}

如果我们现在假设有一个 ID 为 1(长整型)的 User 类型实体,则输出为:

Thread 16 EXECUTES SELECT FOR UPDATE
Hibernate: select user0_.id as id1_31_0_, user0_.player_balance as player_b2_31_0_ from "user" user0_ where user0_.id=? for update
Thread 16 started blocking!
Thread 17 EXECUTES SELECT FOR UPDATE
Hibernate: select user0_.id as id1_31_0_, user0_.player_balance as player_b2_31_0_ from "user" user0_ where user0_.id=? for update
Thread 18 EXECUTES SELECT FOR UPDATE
Hibernate: select user0_.id as id1_31_0_, user0_.player_balance as player_b2_31_0_ from "user" user0_ where user0_.id=? for update
Thread 16 finished blocking!
Thread 16 FINISHED QUERY
Thread 17 FINISHED QUERY
Thread 18 FINISHED QUERY

因此,在调用 entityManager.find(... LockModeType.PESSIMISTIC_WRITE...); 后,此查询的所有后续执行都会等待第一个查询(因为 SELECT ... FOR UPDATE); ,不需要 entityManager.lock(...) 调用。

丢失的异常可能是由于查询超时只是一个提示而导致的,您的数据库可能没有考虑到这一点。请参阅the docs .

QueryTimeoutException: the query takes longer than the specified timeout (see javax.persistence.query.timeout - this property is a hint and might not be followed)

或者也在同一页面上:

javax.persistence.query.timeout query timeout in milliseconds (Integer or String), this is a hint used by Hibernate but requires support by your underlying database (TODO is that 100% true or do we use some other tricks).

所以你不应该依赖超时异常。

关于java - jpa-springEntitymanager.find超时不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43700668/

相关文章:

mysql - Hibernate未插入到mysql : showing insert statements

hibernate - 使用左连接获取重复

mysql - hibernate 、JPA、MySQL : Unable to build EntityManagerFactory

java - Hibernate 4.3.0.Final 和 Spring Data JPA 1.4.3.RELEASE

java - 通过 Hibernate 注解创建外键关系的问题

java - 如何打印字符串中第一个重复出现的字符?

java - 我是否必须使用 Mac 来为 iOS 开发 JavaFX

java - 逐行读取,在java中输入到数组中

java - 获取对象中的 SUM 值

java - 如何在 spring-boot 中禁用 spring-data-mongodb