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:
parent
22f47cd382
commit
48c6dd40d4
|
@ -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)
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue