iDempiere uses a sophisticated persistent object (PO) framework that provides object-relational mapping between Java classes and database tables. Every table in the Application Dictionary is represented by a generated Java class.
package org.compiere.model;import java.sql.ResultSet;import java.sql.Timestamp;import java.math.BigDecimal;import java.util.Properties;/** * Abstract base class for Persistent Object. * Provides CRUD operations, validation, and event handling. */public abstract class PO implements Serializable, Comparator<Object>, Evaluatee, Cloneable { /** Context properties */ protected Properties p_ctx; /** Transaction name */ protected String p_trxName; /** New record flag */ private boolean m_newRecord = false; /** Array of new values */ protected Object[] m_newValues; /** Array of old values for change detection */ protected Object[] m_oldValues; /** Query timeout in seconds */ private Integer m_queryTimeout = null; /** * Set value - performs validation and change tracking * @param columnName column name * @param value new value * @return true if value was set */ protected boolean set_Value(String columnName, Object value); /** * Get value from internal storage * @param columnName column name * @return value or null */ protected Object get_Value(String columnName); /** * Save record to database * @return true if saved successfully */ public boolean save(); /** * Delete record from database * @return true if deleted successfully */ public boolean delete(boolean force); /** * Load record from database * @param ID record ID * @return true if loaded successfully */ protected boolean load(int ID);}
The PO class uses parallel arrays (m_newValues and m_oldValues) for efficient change detection, enabling dirty field tracking and optimized SQL updates.
Auto-generated from Application Dictionary, contains getters/setters:
/** * Generated Model for C_Order * DO NOT MODIFY - Regenerated when table definition changes */public class X_C_Order extends PO { /** Column name C_Order_ID */ public static final String COLUMNNAME_C_Order_ID = "C_Order_ID"; /** Column name DocumentNo */ public static final String COLUMNNAME_DocumentNo = "DocumentNo"; /** Column name GrandTotal */ public static final String COLUMNNAME_GrandTotal = "GrandTotal"; /** * Get Order ID * @return C_Order_ID */ public int getC_Order_ID() { Integer ii = (Integer)get_Value(COLUMNNAME_C_Order_ID); if (ii == null) return 0; return ii.intValue(); } /** * Set Document No * @param DocumentNo Document sequence number */ public void setDocumentNo(String DocumentNo) { set_Value(COLUMNNAME_DocumentNo, DocumentNo); } /** * Get Document No * @return Document sequence number */ public String getDocumentNo() { return (String)get_Value(COLUMNNAME_DocumentNo); } /** * Set Grand Total * @param GrandTotal Total amount of document */ public void setGrandTotal(BigDecimal GrandTotal) { set_Value(COLUMNNAME_GrandTotal, GrandTotal); } /** * Get Grand Total * @return Total amount of document */ public BigDecimal getGrandTotal() { BigDecimal bd = (BigDecimal)get_Value(COLUMNNAME_GrandTotal); if (bd == null) return Env.ZERO; return bd; }}
Never modify X_* classes directly. They are regenerated when the Application Dictionary changes, and all custom modifications will be lost.
// Present in every table for tenant isolationpublic interface TenantColumns { String COLUMNNAME_AD_Client_ID = "AD_Client_ID"; // Client/Tenant ID String COLUMNNAME_AD_Org_ID = "AD_Org_ID"; // Organization ID}
The PO base class automatically sets AD_Client_ID and AD_Org_ID from the context when creating new records, enforcing multi-tenancy at the object level.
The PO class provides methods to detect field changes:
MOrder order = new MOrder(ctx, orderId, trxName);// Check if any field changedif (order.is_Changed()) { log.info("Order has unsaved changes");}// Check specific fieldif (order.is_ValueChanged("C_BPartner_ID")) { int oldPartnerId = order.get_ValueOldAsInt("C_BPartner_ID"); int newPartnerId = order.getC_BPartner_ID(); log.info("Partner changed from " + oldPartnerId + " to " + newPartnerId);}// Get old valueBigDecimal oldTotal = (BigDecimal)order.get_ValueOld("GrandTotal");// Get changed column namesString[] changedColumns = order.get_ColumnsChanged();for (String column : changedColumns) { log.info("Changed: " + column);}