繁体   English   中英

Spring data - 启用乐观锁定

[英]Spring data - enable optimistic locking

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


根据 jpa 规范,只要实体具有@Version注释字段,就应该在实体上自动启用乐观锁定。

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

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

这是正常的还是我错过了什么? 我们是否有义务注释我们所有的方法(或创建一个获取锁的基本存储库实现)以使用 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> {
}

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

您指的是JPA规范第93页的P2 本节开始于:

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

但是您的测试不会创建这种情况。 方法lock永远不会被调用。 因此,不会发生任何相关的锁定。 特别是仅加载实体并不会对其调用lock

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

如果以其他方式更新或删除了版本控制的对象,则即使未对EntityManager.lock进行任何显式调用,实现也必须确保满足LockModeType.OPTIMISTIC_FORCE_INCREMENT的要求。

注意:您正在使用相同的存储库生成两个线程,从而使它们使用相同的EntityManager 我怀疑EntityManager是否支持此功能,而且我不确定您是否真的以这种方式进行了两次交易,但这又是一个问题。

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

乐观锁背后的原因是为了防止从先前状态更新表。 例如:

  1. 您获得ID为1的用户
  2. 另一个用户更新ID为1的用户并将其提交到新状态
  3. 您更新用户1(在步骤1中加载),然后尝试将其提交到数据库。

在这种情况下,在第3步中,您将覆盖其他人在第2步中所做的更改,而您需要引发异常。

我相信spring是通过@version属性实现的,该属性对应于数据库中的version列。 结果是这样的:

update users set password="p2" where id=1 and version=1; 

我认为spring实际上使用字符串作为版本,但是我不确定。 也可能是时间戳记,但这是一般想法。

您不会得到异常,因为只有一个线程正在处理数据。 您在线程1中读取它,而当前版本为1,然后在线程2中读取它-版本仍为1。然后,当您尝试保存它时,将休眠会话中的版本与数据库中的版本进行比较,并且它们匹配-一切都井井有条,因此它将继续正常运行。 尝试将其设置为updateWithSleep(),则应获取预期的异常。

您可以将乐观锁定策略设置为:

乐观锁定(可选-默认为版本):确定乐观锁定策略。

乐观锁定策略:版本:检查版本/时间戳列,全部:检查所有列,脏:检查更改的列无:不使用乐观锁定

乐观锁定完全由Hibernate处理。

乐观锁概念

使用方案:在事务结束时很少进行并发更新时,检查是否由任何其他事务更新

可以使用乐观锁定来处理并发更新。 乐观锁定通过检查自读取以来要更新的数据是否已被另一个事务更改来进行工作。 例如,您搜索了一条记录,经过很长时间后,您将要修改该记录,但与此同时,该记录已被其他人更新。 实现乐观锁定的一种常用方法是在每个表中添加一个版本列,应用程序每次更改一行时都会增加该列。 每个UPDATE语句的WHERE子句都会检查版本号自读取以来是否未更改。 如果行已被另一个事务更新或删除,则应用程序可以回滚该事务并重新开始。 乐观锁定之所以得名,是因为它假设并发更新很少,而应用程序会检测并从中恢复,而不是阻止并发更新。 乐观锁模式仅在用户尝试保存更改时才检测到更改,仅当重新开始对用户没有负担时,它才能很好地起作用。 当实现用例时,由于不得不放弃几分钟的工作而使用户非常恼火,一个更好的选择是使用悲观锁。

@Version批注用作数据库中的一列,应将其添加到每个实体以对该实体执行乐观锁定,例如

@Entity
public class User {
    @Version
    @Column(nullable = false)
    private Long version;
}

这样可以确保不会使用错误的版本创建任何用户。 这意味着您不能同时从多个来源更新用户。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM