我正在尝试使用 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/