title: How to deal with optimistic locking failure in GORM
body: <p>I get optimistic locking failure error when our system comes under heavy load, dozens of concurrent transactions. Application is load balanced over multiple servers communicating with a single database instance.</p>
  
  <p>I understand that at the core of this is GORM optimistic locking mechanism. New thread changes domain object property while it is loaded in original thread and change attempted.</p>
  
  <p>If so, what would be the best way to deal with it? I do not want to have to handle the error because rolling the entire transaction back is not a good solution for.</p>
  
  <p>What are my options?</p>
  
  <ol>
  <li><p>Declaring version false? Would that prevent optimistic locking on that domain object and allow concurrent threads to change it, even if that means overwriting one another without an error?</p></li>
  <li><p>Locking domain object for the duration of the transaction by retrieving it with Object.lock(id)? How significant would be a performance hit, hard to say without testing, but is this the best way to go about it?</p></li>
  <li><p>Any other options?</p></li>
  </ol>
  
  <p>Relevant code included below. If locking is the best way to do it, how do I accomplish it since Object is accesses as child from unsaved parent object?</p>
  
  <p>That line #390 in ProductOrderService.</p>
  
  <pre><code>private rollbackInventory(Order order) {
      order.items.each {orderItem-&gt;
          Product product = orderItem.product
          product.increaseQuantityBy(orderItem.qty)
          product.save(flush: true)
      }
  }
  </code></pre>
  
  <p>And increaseQuantityBy method in Product domain class looks like this:</p>
  
  <pre><code>.......
  
  Integer qty
  
  .......
  
  public void increaseQuantityBy(Integer change) {
      changeQuantityBy(change)
  }
  
  public void decreaseQuantityBy(Integer change) {
      changeQuantityBy(-change)
  }
  
  private void changeQuantityBy(Integer change) {
      qty += change
  }
  </code></pre>
  
  <p>This is the error:</p>
  
  <pre><code>2016-04-29 08:08 -- ERROR-grails.app.controllers.com.shop.order.ProductOrderController:Exception occurred. Object of class [com.shop.Product] with identifier [5774]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.shop.Product#5774]
  org.springframework.orm.hibernate4.HibernateOptimisticLockingFailureException: Object of class [com.shop.Product] with identifier [5774]: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.shop.Product#5774]
          at com.shop.order.ProductOrderService$_rollbackInventory_closure5.doCall(ProductOrderService.groovy:390)
          at com.shop.order.ProductOrderService.rollbackInventory(ProductOrderService.groovy:387)
          at com.shop.order.ProductOrderService.processOrder(ProductOrderService.groovy:178)
          at com.shop.order.ProductOrderController.order(ProductOrderController.groovy:240)
          at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:198)
          at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63)
          at grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter.doFilter(GrailsAnonymousAuthenticationFilter.java:53)
          at grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter.doFilter(RequestHolderAuthenticationFilter.java:53)
          at grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.java:62)
          at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
          at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
          at java.lang.Thread.run(Thread.java:745)
  Caused by: org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.shop.Product#5774]
          ... 12 more
  </code></pre>