草庐IT

java - 通过具体(Java)示例进行乐观锁定

coder 2023-05-16 原文

我花了整个上午阅读所有关于Google churns up on optimistic locking的热门文章,而对于我的一生,我仍然一无所知。

我了解乐观锁定涉及添加一列来跟踪记录的“版本”,并且该列可以是时间戳记,计数器或任何其他版本跟踪构造。但是我仍然不了解如何确保WRITE完整性(这意味着,如果多个进程同时更新同一实体,那之后,该实体将正确反射(reflect)其应处于的真实状态)。

有人可以提供具体的,易于理解的示例,说明如何在Java中使用乐观锁定(例如,针对MySQL数据库)。假设我们有一个Person实体:

public class Person {
    private String firstName;
    private String lastName;
    private int age;
    private Color favoriteColor;
}

并将Person实例持久化到people MySQL表中:
CREATE TABLE people (
    person_id PRIMARY KEY AUTO_INCREMENT,
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,        # } I realize these column defs are not valid but this is just pseudo-code
    age INT NOT NULL,
    color_id FOREIGN KEY (colors) NOT NULL  # Say we also have a colors table and people has a 1:1 relationship with it
);

现在,假设有2个软件系统或1个具有2个线程的系统正在尝试同时更新相同的Person实体:
  • 软件/线程1正在尝试保留姓氏更改(从“John Smith”更改为“John Doe”)
  • 软件/线程2正在尝试保留喜欢的颜色(从红色到绿色)的变化

  • 我的问题:
  • 如何在people和/或colors表上实现乐观锁定? (寻找特定的DDL示例)
  • 然后您如何在应用程序/Java层利用这种乐观锁定? (寻找特定的代码示例)
  • 有人可以帮助我解决DDL/代码更改(来自上述#1和#2)在我的方案(或任何其他方案)中起作用并且会正确地“乐观地锁定” people/colors表的情况吗?基本上,我希望看到行动中的乐观锁定,并对其操作原理进行简单易懂的解释。
  • 最佳答案

    通常,当您考虑乐观锁定时,还可以使用像Hibernate之类的库或其他具有 @Version 支持的JPA-Implementation。

    示例可能如下所示:

    public class Person {
        private String firstName;
        private String lastName;
        private int age;
        private Color favoriteColor;
        @Version
        private Long version;
    }
    

    如果您使用的框架不支持@Version注释,那么显然没有必要添加它。

    DDL可能是

    CREATE TABLE people (
        person_id PRIMARY KEY AUTO_INCREMENT,
        first_name VARCHAR(100) NOT NULL,
        last_name VARCHAR(100) NOT NULL,        # } I realize these column defs are not valid but this is just pseudo-code
        age INT NOT NULL,
        color_id FOREIGN KEY (colors) NOT NULL,  # Say we also have a colors table and people has a 1:1 relationship with it
        version BIGINT NOT NULL
    );
    

    版本会怎样?
  • 每次存储实体之前,都要检查存储在数据库中的版本是否仍然是您知道的版本。
  • 如果是,请存储您的数据,其版本应增加一个

  • 为了完成两个步骤,而又不冒其他进程在两个步骤之间更改数据的风险,通常通过类似以下语句来处理

    UPDATE Person SET lastName = 'married', version=2 WHERE person_id = 42 AND version = 1;
    

    执行该语句后,您检查是否更新了行。如果您这样做了,那么自从您读取数据以来,没有其他人会更改数据,否则其他人会更改数据。如果其他人更改了数据,则通常您所使用的库会收到一个 OptimisticLockException

    此异常应导致所有更改都被撤消,并且由于要更新实体的条件可能不再适用,因此更改值以重新启动的过程将不再适用。

    所以没有冲突:
  • 进程A读取Person
  • 进程A写人,从而增加版本
  • 进程B读取Person
  • 进程B写人,从而增加版本

  • 碰撞:
  • 进程A读取Person
  • 进程B读取Person
  • 进程A写人,从而增加版本
  • 进程B在尝试保存为自读Person以来的版本更改时收到异常

    如果Color是另一个对象,则应按照相同的方案在其中放置一个版本。

    什么不是乐观锁?
  • 乐观锁定并不是合并冲突更改的魔力。乐观锁将仅防止进程意外覆盖另一个进程的更改。
  • 乐观锁实际上不是真正的DB锁。它只是通过比较version列的值来工作的。您不会阻止其他进程访问任何数据,因此希望您得到OptimisticLockException

  • 哪种列类型用作版本?

    如果许多不同的应用程序访问您的数据,则最好使用数据库自​​动更新的列。例如对于MySQL

    version TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
    

    这样,实现乐观锁定的应用程序将注意到哑应用程序的更改。

    如果您更新实体的频率超过了TIMESTAMP的分辨率或Java解释的频率,则此方法可能无法检测到某些更改。另外,如果让Java生成新的TIMESTAMP,则需要确保所有运行您的应用程序的机器都处于完美的时间同步状态。

    如果所有应用程序都可以更改为整数,则长,...版本通常是一个好的解决方案,因为它永远不会受到时钟设置不同的困扰;-)

    还有其他方案。您可以例如每次更改行时,都使用哈希,甚至随机生成String。重要的是,在任何进程保存用于本地处理的数据或在缓存内部时,不要重复值,因为该进程将无法通过查看版本列来检测更改。

    作为最后的选择,您可以将所有字段的值用作版本。尽管这在大多数情况下将是最昂贵的方法,但它是在不更改表结构的情况下获得相似结果的一种方法。如果使用Hibernate,则使用 @OptimisticLocking -annotation强制执行此行为。如果由于您已读取实体而导致任何行更改,则在实体类上使用@OptimisticLocking(type = OptimisticLockType.ALL)失败,或者在另一个进程也更改了您更改的字段时,@OptimisticLocking(type = OptimisticLockType.DIRTY)失败。

    关于java - 通过具体(Java)示例进行乐观锁定,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21120043/

    有关java - 通过具体(Java)示例进行乐观锁定的更多相关文章

    1. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    2. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

      在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

    3. ruby - 通过 rvm 升级 ruby​​gems 的问题 - 2

      尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub

    4. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

      我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

    5. ruby - 通过 erb 模板输出 ruby​​ 数组 - 2

      我正在使用puppet为ruby​​程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby​​不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这

    6. ruby - 通过 ruby​​ 进程共享变量 - 2

      我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

    7. ruby - 通过 RVM (OSX Mountain Lion) 安装 Ruby 2.0.0-p247 时遇到问题 - 2

      我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search

    8. java - 等价于 Java 中的 Ruby Hash - 2

      我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

    9. ruby-on-rails - Enumerator.new 如何处理已通过的 block ? - 2

      我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m

    10. ruby - 如何进行排列以有效地定制输出 - 2

      这是一道面试题,我没有答对,但还是很好奇怎么解。你有N个人的大家庭,分别是1,2,3,...,N岁。你想给你的大家庭拍张照片。所有的家庭成员都排成一排。“我是家里的friend,建议家庭成员安排如下:”1岁的家庭成员坐在这一排的最左边。每两个坐在一起的家庭成员的年龄相差不得超过2岁。输入:整数N,1≤N≤55。输出:摄影师可以拍摄的照片数量。示例->输入:4,输出:4符合条件的数组:[1,2,3,4][1,2,4,3][1,3,2,4][1,3,4,2]另一个例子:输入:5输出:6符合条件的数组:[1,2,3,4,5][1,2,3,5,4][1,2,4,3,5][1,2,4,5,3][

    随机推荐