【问题标题】:Grails org.hibernate.StaleObjectStateException for simultaneous requests同时请求的 Grails org.hibernate.StaleObjectStateException
【发布时间】:2014-07-25 11:21:40
【问题描述】:

我有一个带有控制器的应用程序。执行控制器的操作会更新数据库字段。当一次完成一个动作时,它可以正常工作。但是当我同时做两个动作时,我得到了org.hibernate.StaleObjectStateException 错误

Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

我需要控制器能够处理同时执行的操作,例如将请求排队并按顺序执行。这有可能吗?还是我需要自己处理?

(会在这里使用synchronized 帮助吗?这是正确的方式吗?)

数据库是内存/文件,因为我很少使用它。不需要运行完整的数据库。

编辑:调用“层次结构”如下:

这是入口点 - 控制器。

GameController
  def doTurn() {
    String login = params.login


    // From stackoverflow
    def c = Player.createCriteria()
    def player = c.get {
        eq "login", login
        lock true
    }

    // def player = Player.findAllByLogin(login).get(0)
    PlayerChoice choice = Utils.intToChoice(Integer.parseInt(params.choice))
    int turnNumber = Integer.parseInt(params.turn)

    gameFlowService.makeTurn(player, choice, turnNumber)

    def res = new Utils().playerStateToJSON(player)
    render res as JSON
  }

它调用gameFlowService:

GameFlowService{
  def makeTurn(Player player, PlayerChoice choice, int turnNumber) {
   (...)
    Game currentGame = player.currentGame
    Turn lastTurn = currentGame.getLastTurn()
    // I do stuff with Turn (if it exists) or create it
      currentGame.createTurn()
      currentGame.getLastTurn().makeChoice(player, choice)
    if (currentGame!=null){
        currentGame.save(flush: true, failOnError: true)
    }
}

游戏:

Game {
  Player player1
  Player player2

  int turnsToBePlayed

  List turns
  static hasMany = [turns: Turn]
  static belongsTo = [tournament: Tournament]

  public void createTurn() {
    Turn turn = new Turn([game      : this,
                          turnNumber: turns.size(),
                          player1   : player1,
                          player2   : player2,
                          choice1   : PlayerChoice.CHOICE_NOT_SET,
                          choice2   : PlayerChoice.CHOICE_NOT_SET,
                          points1   : 0,
                          points2   : 0])

    this.addToTurns(turn)
    this.save(flush: true, failOnError: true)
  }
}

然后转身:

class Turn {
    int turnNumber
    boolean completed = false

    Player player1
    Player player2

    PlayerChoice choice1
    PlayerChoice choice2

    int points1
    int points2

    static belongsTo = [game: Game]

    public void makeChoice(Player player, PlayerChoice playerChoice){
      // This method only changes Turn's member variables
    }
}

我只在添加新回合时保存 Game 实例 - 我被引导相信 Hibernate 会自动保留任何进一步的更改。

尽管使用了锁,我仍然得到同样的错误(stacktrace 说它在游戏中在线发生:this.save(flush: true, failOnError: true)

编辑 2: 我相信我的问题并不少见。控制器操作会导致数据库发生变化,并且需要以某种方式进行序列化。 Hibernate/Grails 是如何解决的?

【问题讨论】:

  • 显示一些 COOOOOOODDDDDEEEEE!

标签: database multithreading hibernate grails


【解决方案1】:

我认为您的问题如下,考虑到 2 个同时访问数据库的线程:

  1. A 检索对象
  2. B 检索对象
  3. 更新对象
  4. 保存对象
  5. B 更新对象
  6. B 保存对象 -> StaleObjectStateException

从数据库中检索对象时可以锁定它。这会导致“选择...进行更新”,请参阅此链接以了解用法:

http://grails.org/doc/latest/ref/Domain%20Classes/lock.html

但是为了避免任何竞争条件,你必须使用 lock 方法作为你的 getter,像这样:

def airport = Airport.lock(10) // lock for update
airport.name = "Heathrow"
airport.save()

如果必须查询对象,可以在 Criteria 中使用 lock。

这是 Criteria 的示例:

def c = Airport.createCriteria()
c.get {
  eq "name", name
  lock true
}

【讨论】:

  • 锁定是否适用于整个域层次结构?例如我从数据库中检索了一个 Player 对象,它拥有一个 Game 对象,而后者又拥有一个 Turn 对象。 3 个都被锁定了吗(因为我可能会全部更新)?
  • 我像 def player = Player.findAllByLogin(login).get(0) 这样检索对象 .. 之后调用 player.lock() 是否足够?
  • 你为什么不用Player.findByLogin login
  • lock() 与您的问题无关。它仅在当前休眠会话中锁定实例。
  • 在获取后调用 lock 可能会导致问题,因为在检索对象和锁定它之间有一段时间。您可以使用 Criteria 查询对象并在 Criteria 内使用“lock true”。它不仅将对象锁定在会话中,Hibernate 文档对此是这么说的:升级锁。以这种锁定模式加载的对象是使用 SQL select ... for update 实现的。
猜你喜欢
  • 2014-02-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-07
  • 2012-05-12
  • 1970-01-01
相关资源
最近更新 更多