弹簧 JPA 锁

2022-09-01 12:17:00

在锁定春季jpa时,我真的很不安全。
因此,请考虑将此问题作为澄清。我真的希望我理解正确,但我的英语对于理解复杂的博客文章来说不是很好。
所以这就是我认为我从一些文章中得到的:

锁定有两种基本类型:

  • 当计划较少的写入操作时,应使用乐观。读取操作不会锁定对象。
    例如:您在具有乐观锁的实体中有一个“货币余额”浮动。现在,两个进程读取此值并将其用于计算和填充。其中一个现在更改了该值,并通过更新将其写入数据库。到目前为止没有错误。
    但现在另一个进程也会更改该值并希望对其进行更新。现在有一个错误。这仅仅是因为第二次更新而发生的。
    如果第二个进程删除了该实例,则不会发生任何事情。
  • 在计划大量写入操作时,应使用悲观。读取操作将锁定对象。
    例如:您在具有悲观锁的实体中有一个“货币余额”浮动。一个进程使用“findOne”读取数据/值。
    在此之后,另一个进程也想要读取数据,使用乐观锁可以实现,但是使用悲观锁,他现在必须等待(没有错误,只是等待)。
    当进程 1 准备就绪(更改值并更新它)时,进程 2 可以继续。

第一个问题:到目前为止,这是正确的吗?当我现在想测试这个knollage时,我可以在这个LockModeType之间进行选择:

  • 乐观的
  • OPTIMISTIC_FORCE_INCREMENT
  • PESSIMISTIC_READ
  • PESSIMISTIC_WRITE
  • PESSIMISTIC_FORCE_INCREMENT

为什么现在有这么多子锁,它们有什么作用?当“乐观”是我试图理解的高层的乐观锁,那么什么是“OPTIMISTIC_FORCE_INCEMENT”?
版本更新与此有什么关系?(或 ?@version

继续:
在Spring jpa中,锁有三个基本用途:

  1. 在普通列上,例如:

    @Entity
    public class PersonEntity {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        @Lock(LockModeType.PESSIMISTIC_WRITE)
        private String name;
    }  
    
  2. 在外键上转到其他表,例如:

    @Entity
    public class PersonEntity {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
    
        @Lock(LockModeType.PESSIMISTIC_WRITE)
        @OneToOne
        private Group group;
    }  
    
  3. 在存储库内的表上,例如:

    interface PersonRepository extends Repository<Person, Long> {
        @Lock(LockModeType.PESSIMISTIC_WRITE)
        Person findOne(Long id);
    }  
    

直接锁定实体

@Entity
@Lock(LockModeType.PESSIMISTIC_WRITE)
public class PersonEntity { }

是不可能的。因此,您可以使用 3(锁定在存储库内)。

第二个问题:这是正确的吗?我忘了使用锁定吗?

继续:
锁定背后的想法是,其他方法/进程必须等到锁定被释放(除了乐观锁,这里抛出一个错误)。
只要实例/对象处于活动状态或直到下一次提交,锁定就在那里。
解锁对象有两种主要可能性:

  1. 在事务中:在此完整方法中,锁处于活动状态。但是当返回时,锁将被移除。

    @Transactional
    public void test(){
        System.out.println("start, but not locked yet");
        PersonEntity person1 = personRepository.findOne(1L); // locks this person or must wait, when locked
        System.out.println("now locked");
        // do something
        return true; // now the lock will be deleted -> unlocked again
    }  
    
  2. 直到对象被删除:选择对象时数据将被锁定,删除对象时数据将被解锁。

    public void test(){
        System.out.println("start, but not locked yet");
        PersonEntity person1 = personRepository.findOne(1L); // locks this person or must wait, when locked
        System.out.println("now locked");
        // do something
        person1 = null; // now the lock will be deleted -> unlocked again
        System.out.println("not locked anymore");
        // do something more
        return true;
    }  
    

第三个问题:到目前为止,这是正确的吗?这个函数真的可以等待数据被锁定吗?当对象设置为 ?null

最后一句话:
我真的希望我不会惹恼别人。但就像我说的:我真的很难理解英语中如此复杂的结构:(
所以非常感谢任何形式的帮助:)我真的很感激任何小小的帮助。无论你是给我链接以便更多理解还是直接回答我的问题:)


答案 1

锁定

锁定用于防止脏读取(读取尚未提交的数据)和非可重复读取(读取在一个读取完成之前被另一个事务删除的数据)。

乐观的

乐观锁定将在提交事务时锁定资源。乐观锁定将在首次访问时保存数据的状态。因此,在乐观锁定中,您可以并发访问。如果要执行乐观操作,则在操作之前,将初始状态与当前状态进行比较。如果存在差异(在此期间已修改资源),则不会提交事务。

乐观的力增量

对于受版本控制的对象,这将递增对象的版本号。在未版本化的对象上,将引发异常。

悲观

悲观阅读

对于可重复读取,用于确保数据在两次读取之间不会更新。它是一个共享锁,这意味着不同的进程可以执行读取操作(不允许写入操作)。

悲观写作

强制序列化更新的独占锁。在乐观锁定仅保存状态的情况下,此处将其锁定以防止事务失败/死锁,以防在并发操作发生这种情况的情况下。

悲观的写入力增量

类似于它的乐观对应物,一个悲观的写入,更新对象的版本。为不受版本控制的对象引发异常。


答案 2

我遇到了所有这些问题,我正在发布一个我实际上尝试过的例子,如果它有帮助的话。我有一个设备实体,DeviceStatus实体(与设备一对一),配置实体(与设备一对多)。我有两个更新设备状态的路径(来自mqtt消息处理程序和http请求)。

以下方法在我的 DeviceStatus Repository 中

@Lock(LockModeType.PESSIMISTIC_WRITE)
DeviceStatus findByDeviceMacAddress(String macAddress);    //used by the mqtt handler

@Lock(LockModeType.PESSIMISTIC_WRITE)
List<DeviceStatus> findByDeviceConfigurationId(Long deviceConfigurationId);    //used by the http request handler

如果 http 请求处理程序调用第一个,则获取PESSIMISTIC_WRITE,在事务完成之前,mqtt 处理程序无法获取PESSIMISTIC_WRITE锁定(两个处理程序都尝试检索相同的 DeviceStatus 实体)。这意味着调用 将等到 http 请求处理程序完成事务。此外,如果 mqtt 处理程序是第一个调用 的处理程序,则 http 请求处理程序必须等到 mqtt 处理程序完成其事务。findByDeviceConfigurationIdfindByDeviceMacAddressfindByDeviceMacAddressfindByDeviceMacAddress

我知道这不是一个完整的答案,但认为它可能会帮助你。


推荐