1.error occured when change a row's user DDLB filed and save, below error occured:


This is caused by the various data type between java cached old row data and the DB data, the java cached row data of sales_rep_is java.lang.String, but it is java.lang.Integer for the pulling data from DB, the research result is as below:

Table info:


Field:sales_rep_id numeric(10,0) not null,

AD table: c_my

AD Column:sales_rep_id , below reference setting will display user list for user to select on GUI.


select AD_Reference_ID,AD_Reference_value_ID,name,* from ad_column where ad_table_id = 1000012 and name = 'sales_rep_id'

AD_Reference_ID:18 ,AD_Reference_value_ID:110


Code: GridTable.dataSavePO()

Process: Compare GUI data, Object[] rowData, with the cached old row data, private object [] m_rowData, if find the difference then consider user changed data, then compare the m_rowData with the current dbValue, if it is inserting or compare db parameter is off then ignore this step, the system will report an error if there are data difference between cache old data(m_rowData) and dbValue.

This error was posted at above step, the java data type variance, String vs Integer, caused this, the db data has no changed in fact at that time, so we will go to see why the cache data was changed to String, but the checked DB data is Integer, Integer should be right for our table column sales_rep_id data type.


The m_rowData was initialized in below code:


public final void setValueAt (Object value, int row, int col, boolean force){


Object[] rowData = getDataAtRow(row);


if (m_rowData == null)


int size = m_fields.size();

m_rowData = new Object[size];

for (int i = 0; i < size; i++)

m_rowData[i] = rowData[i];




We can see, the m_rowData will be initialized firstly when user changed a value and setValueAt() was triggered. Its data came from the Object[] rowData, then we trace to getDataAtRow().

Step3: getDataAtRow

private Object[] getDataAtRow(int row, boolean fetchIfNotFound){


fillBuffer(row, DEFAULT_FETCH_SIZE);


rowData = (Object[])m_virtualBuffer.get(sort.index);


rowData comes from m_virtualBuffer, one data cache Map, continue to fillBuffer()


private void fillBuffer(int start, int fetchSize){


Object[] data = readData(rs);


m_virtualBuffer.put((Integer)data[m_indexKeyColumn], data);




private Object[] readData (ResultSet rs){


// Column Info

GridField field = (GridField)m_fields.get(j);

columnName = field.getColumnName();




displayType = field.getDisplayType();

// Integer, ID, Lookup (UpdatedBy is a numeric column)

if (displayType == DisplayType.Integer

|| (DisplayType.isID(displayType)

&& (columnName.endsWith("_ID") || columnName.endsWith("_Acct")

|| columnName.equals("AD_Key") || columnName.equals("AD_Display")))

|| columnName.endsWith("atedBy"))


rowData[j] = new Integer(rs.getInt(j+1)); // Integer

if (rs.wasNull())

rowData[j] = null;

}else if...

}else if ...

// String


rowData[j] = rs.getString(j+1); // String



We can see the field 'sales_rep_id' will go to the final to getString, although the isID() will return true but it does not end with "_ID", it ends with "_id", then the data in java cache is java.lang.String.

And where the 'displayType' comes from? We need to trace data source of m_fields.

We can find some clue in GridTable.addField():

public void addField (GridField field)




} // addColumn



GridFieldVO voF = (GridFieldVO)m_vo.getFields().get(f);


GridField field = new GridField (voF);


//  Add field


GridTabVO.getFields()-->createFields()--&gt; GridFieldVO.create()--&gt;

GridFieldVO vo = new GridFieldVO (ctx, WindowNo, TabNo,  AD_Window_ID, AD_Tab_ID, readOnly);

if (columnName.equalsIgnoreCase("AD_Reference_ID")) vo.displayType = rs.getInt (i);

We can see the display type comes from ad_column.ad_reference_id, 'DisplayType.Table',it is 18 for sales_rep_id

Step6:But Why the db filed is Integer in step1?

DB data comes from PO, below is data segment in dataSavePO():

PO po = null;

if (Record_ID != -1)

po = table.getPO(Record_ID, null);

else // Multi - Key

po = table.getPO(getWhereClause(rowData), null);


This method will return an GenericPO or your PO modal object, we created Mmy.java in this sample, the contructor as below:

public class Mmy extends X_c_my {

public Mmy(Properties ctx, int c_my_ID, String trxName) {

super(ctx, c_my_ID, trxName);



public class X_c_my extends PO implements I_c_my, I_Persistent


/** Standard Constructor */

public X_c_my (Properties ctx, int c_my_ID, String trxName)


super (ctx, c_my_ID, trxName);



public PO (Properties ctx, int ID, String trxName, ResultSet rs)

1)Get PO metadata info

p_info = initPO(ctx)--&gt;


new POInfo()--&gt;POInfo.loadInfo()--&gt;

Loop to get new POInfoColumn ():

DisplayType = displayType(AD_Reference_ID);

if (columnName.equals("AD_Language") || columnName.equals("EntityType"))


DisplayType = org.compiere.util.DisplayType.String;

ColumnClass = String.class;


else if (columnName.equals("Posted")

|| columnName.equals("Processed")

|| columnName.equals("Processing"))


ColumnClass = Boolean.class;


else if (columnName.equals("Record_ID"))


DisplayType = org.compiere.util.DisplayType.ID;

ColumnClass = Integer.class;



ColumnClass = org.compiere.util.DisplayType.getClass(displayType, true);


if (isText(displayType) || displayType == List)

return String.class;

else if (isID(displayType) || displayType == Integer)    //  note that Integer is stored as BD

return Integer.class;

else if (isNumeric(displayType))

return java.math.BigDecimal.class;

else if (isDate(displayType))

return java.sql.Timestamp.class;

else if (displayType == YesNo)

return yesNoAsBoolean ? Boolean.class : String.class;

else if (displayType == Button)

return String.class;

else if (isLOB(displayType)) // CLOB is String

return byte[].class;


return Object.class;

DisplayType.isID ()

public static boolean isID (int displayType)


if (displayType == ID || displayType == Table || displayType == TableDir

|| displayType == Search || displayType == Location || displayType == Locator

|| displayType == Account || displayType == Assignment || displayType == PAttribute

|| displayType == Image || displayType == Color)

return true;

return false;


So we can see sales_rep_id AD_reference_Id is 18-Table, so its class is Integer in POInfo column list.

2)get PO data

PO.load(ID, trxName)--&gt;PO.load(trxName)()--&gt;PO.load (ResultSet rs)

Class<?> clazz = p_info.getColumnClass(index);

int dt = p_info.getColumnDisplayType(index);



if (clazz == Integer.class)

m_oldValues[index] = decrypt(index, new Integer(rs.getInt(columnName)));

So from above, the clazz will be Integer.claa, sales_rep_id java data type will be set to Integer when getting DB data.

Fina result:

For cached old data, answer is in GridTable().readData()

displayType == DisplayType.Integer

|| (DisplayType.isID(displayType)

&& (columnName.endsWith("_ID") || columnName.endsWith("_Acct")

|| columnName.equals("AD_Key") || columnName.equals("AD_Display")))

|| columnName.endsWith("atedBy")

Only when aboe is true then the old cache row data will be Integer, but "sales_rep_id" ends with "_id", not "_ID".

For the comparing DB data, answer is in DisplayType.getClass()

if (isID(displayType) || displayType == Integer)    //  note that Integer is stored as BD

return Integer.class;

isID() will return true, so sales_rep_id was set to Integer

Solving method:

Change column name to "Sales_Rep_ID" Both in create table SQL or the AD Table&Column window is OK, AD table as below: