fixed issue 39756 change lockForNoKeyUpdate to return the instance with the lock
authorSandra Huguet <sandra.huguet@openbravo.com>
Tue, 11 Dec 2018 17:15:41 +0100
changeset 35291 440fd243a83c
parent 35290 71014553054c
child 35292 a67abc7a3690
fixed issue 39756 change lockForNoKeyUpdate to return the instance with the lock

*Retrieves an object from the database getting a lock "for no key update" for the indicated
object. Change method name, parameters and return.
*Improve and modify the javadoc with the required changes.
src/org/openbravo/dal/service/OBDal.java
--- a/src/org/openbravo/dal/service/OBDal.java	Tue Dec 11 17:18:05 2018 +0100
+++ b/src/org/openbravo/dal/service/OBDal.java	Tue Dec 11 17:15:41 2018 +0100
@@ -27,6 +27,8 @@
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 
+import javax.persistence.LockModeType;
+
 import org.apache.log4j.Logger;
 import org.hibernate.ObjectNotFoundException;
 import org.hibernate.Session;
@@ -42,6 +44,7 @@
 import org.openbravo.base.structure.BaseOBObject;
 import org.openbravo.base.structure.ClientEnabled;
 import org.openbravo.base.structure.OrganizationEnabled;
+import org.openbravo.base.util.Check;
 import org.openbravo.dal.core.DalUtil;
 import org.openbravo.dal.core.OBContext;
 import org.openbravo.dal.core.SessionHandler;
@@ -50,6 +53,7 @@
 import org.openbravo.database.SessionInfo;
 import org.openbravo.model.ad.system.Client;
 import org.openbravo.model.common.enterprise.Organization;
+import org.openbravo.service.db.DalConnectionProvider;
 
 /**
  * The OBDal class offers the main external access to the Data Access Layer. The variety of data
@@ -725,4 +729,46 @@
     final Entity e = ModelProvider.getInstance().getEntity(entityName);
     OBContext.getOBContext().getEntityAccessChecker().checkReadable(e);
   }
+
+  /**
+   * Creates a WRITE lock in database for the DAL persistence instance {@code object} parameter and
+   * returns a new instance representing the same database object.
+   * <p>
+   * Note the original instance that is passed as parameter is evicted from Hibernate's 1st level.
+   * Therefore, any state not persisted before invoking this method will be ignored, after invoking
+   * this method the parameter instance shouldn't be used anymore using instead the returned one.
+   * <p>
+   * Whereas this is similar to JPA's {@link LockModeType#PESSIMISTIC_WRITE}, it decreases lock
+   * level in PostgreSQL implemented by Hibernate from {@code FOR UPDATE} to
+   * {@code FOR NO KEY UPDATE} allowing insertions of children records while a lock on its parent is
+   * acquired by a different transaction. This is a workaround until Hibernate issue HHH-13135 is
+   * fixed. Unlike locks acquired by Hibernate, the ones created by this method are only present in
+   * Database and cannot be detected by Hibernate (eg. {@link Session#getCurrentLockMode(Object)}.
+   * 
+   * @param object
+   *          DAL instance to acquire a database lock for.
+   * @return A new DAL instance that represents the same database object than the parameter.
+   */
+  public <T extends BaseOBObject> T getObjectLockForNoKeyUpdate(T object) {
+    Entity entity = object.getEntity();
+
+    Check.isTrue(entity.getIdProperties().size() == 1, "Expected entity with a single ID. "
+        + entity + " has " + entity.getIdProperties().size());
+
+    String rdbms = new DalConnectionProvider(false).getRDBMS();
+    String lockType = "ORACLE".equals(rdbms) ? "UPDATE" : "NO KEY UPDATE";
+
+    String sql = "SELECT " + entity.getIdProperties().get(0).getColumnName() + " FROM "
+        + entity.getTableName() + " WHERE " + entity.getIdProperties().get(0).getColumnName()
+        + " = :id FOR " + lockType;
+
+    Session session = getSession();
+    session.evict(object);
+    session.createNativeQuery(sql).setParameter(BaseOBObject.ID, object.getId()).uniqueResult();
+
+    @SuppressWarnings("unchecked")
+    T newInstance = OBDal.getInstance().get((Class<T>) entity.getMappingClass(), object.getId());
+
+    return newInstance;
+  }
 }
\ No newline at end of file