IDEMPIERE-4250 Implement optimistic locking support (#838)

* IDEMPIERE-4250 Implement optimistic locking support

* IDEMPIERE-4250 Implement optimistic locking support

- merge fix from Carlos.
This commit is contained in:
hengsin 2021-09-03 13:17:20 +08:00 committed by GitHub
parent 22f47cd382
commit 48c6dd40d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 251 additions and 5 deletions

View File

@ -112,11 +112,13 @@ public abstract class PO
/**
*
*/
private static final long serialVersionUID = 571979727987834997L;
private static final long serialVersionUID = 3153695115162945843L;
public static final String LOCAL_TRX_PREFIX = "POSave";
private static final String USE_TIMEOUT_FOR_UPDATE = "org.adempiere.po.useTimeoutForUpdate";
private static final String USE_OPTIMISTIC_LOCKING = "org.idempiere.po.useOptimisticLocking";
/** default timeout, 300 seconds **/
private static final int QUERY_TIME_OUT = 300;
@ -290,6 +292,9 @@ public abstract class PO
/** Immutable flag **/
private boolean m_isImmutable = false;
private String[] m_optimisticLockingColumns = new String[] {"Updated"};
private Boolean m_useOptimisticLocking = null;
/** Access Level S__ 100 4 System info */
public static final int ACCESSLEVEL_SYSTEM = 4;
@ -2548,6 +2553,14 @@ public abstract class PO
List<Object> params = new ArrayList<Object>();
String where = get_WhereClause(true);
List<Object> optimisticLockingParams = new ArrayList<Object>();
if (is_UseOptimisticLocking() && m_optimisticLockingColumns != null && m_optimisticLockingColumns.length > 0)
{
StringBuilder builder = new StringBuilder(where);
addOptimisticLockingClause(optimisticLockingParams, builder);
where = builder.toString();
}
//
boolean changes = false;
StringBuilder sql = new StringBuilder ("UPDATE ");
@ -2785,9 +2798,12 @@ public abstract class PO
}
}
sql.append(" WHERE ").append(where);
/** @todo status locking goes here */
if (log.isLoggable(Level.FINEST)) log.finest(sql.toString());
if (is_UseOptimisticLocking() && optimisticLockingParams.size() > 0)
params.addAll(optimisticLockingParams);
int no = 0;
if (isUseTimeoutForUpdate())
no = withValues ? DB.executeUpdateEx(sql.toString(), m_trxName, QUERY_TIME_OUT)
@ -2827,7 +2843,95 @@ public abstract class PO
return true;
}
}
private void addOptimisticLockingClause(List<Object> optimisticLockingParams, StringBuilder where) {
for(String oc : m_optimisticLockingColumns)
{
int index = get_ColumnIndex(oc);
if (index >= 0)
{
Class<?> c = p_info.getColumnClass(index);
int dt = p_info.getColumnDisplayType(index);
if (DisplayType.isLOB(dt))
continue;
Object value = get_ValueOld(oc);
if (value == null)
{
where.append(" AND ").append(oc).append(" IS NULL ");
}
else if (value instanceof Timestamp)
{
if (dt == DisplayType.Date)
where.append(" AND ").append(oc).append(" = trunc(cast(? as date))");
else
where.append(" AND ").append(oc).append(" = ? ");
optimisticLockingParams.add(value);
}
else if (c == Boolean.class)
{
where.append(" AND ").append(oc).append(" = ? ");
boolean bValue = false;
if (value instanceof Boolean)
bValue = ((Boolean)value).booleanValue();
else
bValue = "Y".equals(value);
optimisticLockingParams.add(encrypt(index,bValue ? "Y" : "N"));
}
else if (c == String.class)
{
if (value.toString().length() == 0) {
where.append(" AND ").append(oc).append(" = '' ");
} else {
where.append(" AND ").append(oc).append(" = ? ");
optimisticLockingParams.add(encrypt(index,value));
}
}
else
{
where.append(" AND ").append(oc).append(" = ? ");
optimisticLockingParams.add(value);
}
}
}
}
/**
*
* @return true if optimistic locking is enable
*/
public boolean is_UseOptimisticLocking() {
if (m_useOptimisticLocking != null)
return m_useOptimisticLocking;
else
return "true".equalsIgnoreCase(System.getProperty(USE_OPTIMISTIC_LOCKING, "false"));
}
/**
* enable/disable optimistic locking
* @param enable
*/
public void set_UseOptimisticLocking(boolean enable) {
m_useOptimisticLocking = enable;
}
/**
*
* @return optimistic locking columns
*/
public String[] get_OptimisticLockingColumns() {
return m_optimisticLockingColumns;
}
/**
* set columns use for optimistic locking (auto add to where clause for update
* and delete)
* @param columns
*/
public void set_OptimisticLockingColumns(String[] columns) {
m_optimisticLockingColumns = columns;
}
private boolean isUseTimeoutForUpdate() {
return "true".equalsIgnoreCase(System.getProperty(USE_TIMEOUT_FOR_UPDATE, "false"))
&& DB.getDatabase().isQueryTimeoutSupported();
@ -3469,15 +3573,27 @@ public abstract class PO
}
// The Delete Statement
String where = get_WhereClause(true);
List<Object> optimisticLockingParams = new ArrayList<Object>();
if (is_UseOptimisticLocking() && m_optimisticLockingColumns != null && m_optimisticLockingColumns.length > 0)
{
StringBuilder builder = new StringBuilder(where);
addOptimisticLockingClause(optimisticLockingParams, builder);
where = builder.toString();
}
StringBuilder sql = new StringBuilder ("DELETE FROM ") //jz why no FROM??
.append(p_info.getTableName())
.append(" WHERE ")
.append(get_WhereClause(true));
.append(where);
int no = 0;
if (isUseTimeoutForUpdate())
no = DB.executeUpdateEx(sql.toString(), localTrxName, QUERY_TIME_OUT);
no = optimisticLockingParams.isEmpty()
? DB.executeUpdateEx(sql.toString(), localTrxName, QUERY_TIME_OUT)
: DB.executeUpdateEx(sql.toString(), optimisticLockingParams.toArray(), localTrxName, QUERY_TIME_OUT);
else
no = DB.executeUpdate(sql.toString(), localTrxName);
no = optimisticLockingParams.isEmpty()
? DB.executeUpdate(sql.toString(), localTrxName)
: DB.executeUpdate(sql.toString(), optimisticLockingParams.toArray(), false, localTrxName);
success = no == 1;
}
catch (Exception e)

View File

@ -35,7 +35,9 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Properties;
import org.adempiere.exceptions.DBException;
import org.compiere.model.MBPartner;
import org.compiere.model.MClient;
import org.compiere.model.MMessage;
import org.compiere.model.MTest;
import org.compiere.model.POInfo;
import org.compiere.util.DB;
@ -319,4 +321,132 @@ public class POTest extends AbstractTestCase
trx3.close();
}
}
@Test
public void testOptimisticLocking()
{
int joeBlock = 118;
MBPartner bp1 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
MBPartner bp2 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
//normal update without optimistic locking
bp1.setDescription("bp1");
boolean updated = bp1.save();
assertTrue(updated);
bp2.setDescription("bp2");
updated = bp2.save();
assertTrue(updated);
//last update ok, description=bp2
bp1 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
bp2 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
assertEquals("bp2", bp1.getDescription());
assertEquals("bp2", bp2.getDescription());
//test update with default optimistic locking using updated timestamp
bp1.set_UseOptimisticLocking(true);
bp1.setDescription("bp1");
updated = bp1.save();
assertTrue(updated);
bp2.set_UseOptimisticLocking(true);
bp2.setDescription("bp2.1");
updated = bp2.save();
assertFalse(updated);
//last update fail, description=bp1
bp1 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
bp2 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
assertEquals("bp1", bp1.getDescription());
assertEquals("bp1", bp2.getDescription());
//test update with custom optimistic locking columns
bp1.set_UseOptimisticLocking(true);
bp1.setDescription("bp1.1");
updated = bp1.save();
assertTrue(updated);
bp2.set_UseOptimisticLocking(true);
bp2.set_OptimisticLockingColumns(new String[] {"Name"});
bp2.setDescription("bp2");
updated = bp2.save();
assertTrue(updated);
//last update ok, description=bp2
bp1 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
bp2 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
assertEquals("bp2", bp1.getDescription());
assertEquals("bp2", bp2.getDescription());
//test update with custom multiple column optimistic locking
bp1.set_UseOptimisticLocking(true);
bp1.setDescription("bp1");
updated = bp1.save();
assertTrue(updated);
bp2.set_UseOptimisticLocking(true);
bp2.set_OptimisticLockingColumns(new String[] {"Name","Description"});
bp2.setDescription("bp2.1");
updated = bp2.save();
assertFalse(updated);
//last update fail, description=bp1
bp1 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
bp2 = new MBPartner(Env.getCtx(), joeBlock, getTrxName());
assertEquals("bp1", bp1.getDescription());
assertEquals("bp1", bp2.getDescription());
MMessage msg1 = new MMessage(Env.getCtx(), 0, getTrxName());
msg1.setValue("msg1 test");
msg1.setMsgText("msg1 test");
msg1.setMsgType(MMessage.MSGTYPE_Information);
msg1.saveEx();
//test normal delete
updated = msg1.delete(true);
assertTrue(updated);
msg1 = new MMessage(Env.getCtx(), 0, getTrxName());
msg1.setValue("msg1 test");
msg1.setMsgText("msg1 test");
msg1.setMsgType(MMessage.MSGTYPE_Information);
msg1.saveEx();
//test delete with default optimistic locking
MMessage msg2 = new MMessage(Env.getCtx(), msg1.getAD_Message_ID(), getTrxName());
msg1.setMsgText("msg 1.1 test");
msg1.saveEx();
msg2.set_UseOptimisticLocking(true);
updated = msg2.delete(true);
assertFalse(updated);
//test delete with custom optimistic locking columns
msg2 = new MMessage(Env.getCtx(), msg1.getAD_Message_ID(), getTrxName());
assertEquals(msg1.getMsgText(), msg2.getMsgText());
msg1.setMsgText("msg1 test");
msg1.saveEx();
msg2.set_UseOptimisticLocking(true);
msg2.set_OptimisticLockingColumns(new String[] {"Value"});
updated = msg2.delete(true);
assertTrue(updated);
//test delete with multiple custom optimistic locking columns
msg1 = new MMessage(Env.getCtx(), 0, getTrxName());
msg1.setValue("msg1 test");
msg1.setMsgText("msg1 test");
msg1.setMsgType(MMessage.MSGTYPE_Information);
msg1.saveEx();
msg2 = new MMessage(Env.getCtx(), msg1.getAD_Message_ID(), getTrxName());
msg1.setMsgText("msg 1.1 test");
msg1.saveEx();
msg2.set_UseOptimisticLocking(true);
msg2.set_OptimisticLockingColumns(new String[] {"Value", "MsgText"});
updated = msg2.delete(true);
assertFalse(updated);
msg2 = new MMessage(Env.getCtx(), msg1.getAD_Message_ID(), getTrxName());
assertEquals(msg1.getMsgText(), msg2.getMsgText());
}
}