弹簧数据 - 启用乐观锁定

2022-09-04 22:19:00

注意:我不需要关于乐观锁定的解释。这个问题是关于在使用乐观锁定时,似乎是什么特定的Spring Data行为。


从 jpa 规范中,每当实体具有带注释的字段时,都应在实体上自动启用乐观锁定。@Version

如果我在使用存储库的春季数据测试项目中执行此操作,则锁定似乎没有被激活。在执行不可重复读取测试时,会抛出事实上的 no(参见 JPA 规范第 93 页上的 P2)OptimisticLockException

但是,从spring文档中我看到,如果我们用注释单个方法,那么底层系统就会正确地抛出一个(然后被spring捕获并以稍微不同的形式向上传播堆栈)。@Lock(LockModeType.OPTIMISTIC)OptimisticLockException

这是正常的还是我错过了什么?我们是否有义务对所有方法进行注释(或创建一个采用锁定的基本存储库实现)以使用spring数据启用乐观行为?

我在 spring boot 项目 1.4.5 版的上下文中使用 spring 数据。

测试:

public class OptimisticLockExceptionTest {

    static class ReadWithSleepRunnable extends Thread {

        private OptimisticLockExceptionService service;

        private int id;

        UserRepository userRepository;

        public ReadWithSleepRunnable(OptimisticLockExceptionService service, int id, UserRepository userRepository) {
            this.service = service;
            this.id = id;
            this.userRepository = userRepository;
        }

        @Override
        public void run() {
            this.service.readWithSleep(this.userRepository, this.id);
        }

    }

    static class ModifyRunnable extends Thread {

        private OptimisticLockExceptionService service;

        private int id;

        UserRepository userRepository;

        public ModifyRunnable(OptimisticLockExceptionService service, int id, UserRepository userRepository) {
            this.service = service;
            this.id = id;
            this.userRepository = userRepository;
        }

        @Override
        public void run() {
            this.service.modifyUser(this.userRepository, this.id);
        }

    }

    @Inject
    private OptimisticLockExceptionService service;

    @Inject
    private UserRepository userRepository;

    private User u;

    @Test(expected = ObjectOptimisticLockingFailureException.class)
    public void thatOptimisticLockExceptionIsThrown() throws Exception {

        this.u = new User("email", "p");
        this.u = this.userRepository.save(this.u);

        try {
            Thread t1 = new ReadWithSleepRunnable(this.service, this.u.getId(), this.userRepository);
            t1.start();
            Thread.sleep(50);// To be sure the submitted thread starts
            assertTrue(t1.isAlive());
            Thread t2 = new ModifyRunnable(this.service, this.u.getId(), this.userRepository);
            t2.start();
            t2.join();
            assertTrue(t1.isAlive());
            t1.join();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

测试服务:

@Component
public class OptimisticLockExceptionService {

    @Transactional
    public User readWithSleep(UserRepository userRepo, int id) {

        System.err.println("started read");
        User op = userRepo.findOne(id);
        Thread.currentThread();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.err.println("read end");
        return op;

    }

    @Transactional
    public User modifyUser(UserRepository userRepo, int id) {

        System.err.println("started modify");
        User op = userRepo.findOne(id);

        op.setPassword("p2");

        System.err.println("modify end");
        return userRepo.save(op);

    }
}

存储库:

@Repository
public interface UserRepository extends CrudRepository<User, Integer> {
}

答案 1

使用 Spring Data JPA 的乐观锁定由所使用的 JPA 实现实现。

您指的是 JPA 规范第 93 页上的 P2。本节开头是:

如果事务 T1 调用受版本控制的对象,则实体管理器必须确保不会发生以下任何一种现象:lock(entity, LockModeType.OPTIMISTIC)

但是,您的测试不会创建这样的方案。该方法永远不会被调用。因此,不会发生相关的锁定。特别是只是加载一个实体不会调用它。locklock

当修改对象时,事情会发生变化(规范的第93页第二段,但最后一段):

如果以其他方式更新或删除了版本化对象,则实现必须确保满足 的要求,即使没有显式调用 也是如此。LockModeType.OPTIMISTIC_FORCE_INCREMENTEntityManager.lock

注意:您正在使用相同的存储库生成两个线程,这反过来又会使它们使用相同的 EntityManager。我怀疑这是否得到EntityManager的支持,我也不确定你是否真的以这种方式获得了两笔交易,但这是另一天的问题。


答案 2

回答你的问题,不。但是,如果您想使用您的实体,并且在 save() 之后需要新版本,那么是的,或者使用 flush()。原因是乐观锁定是在事务的提交(或刷新)时确定的。


推荐