Support for insert into ... returning
Goal: Technical
Developer: Heng Sin Low
Feature Ticket: IDEMPIERE-7204
Version: iDempiere 14
The problem
When saving a new persistent object (PO) in iDempiere, PO.save() generated an INSERT INTO statement and executed it with DB.executeUpdate(...).
To initialize default values, sequence IDs, database defaults, and audit timestamps, the persistence layer immediately executed a separate SELECT query via PO.load(m_trxName):
SELECT * FROM TableName WHERE TableName_ID = ?
When centralized IDs were not used and the primary key was generated by the database sequence, an extra query was also executed to find the generated record ID by its UUID before reloading.
In high-concurrency environments or high-volume data imports (such as REST API batch operations and automated document processing), this separate SELECT round-trip for every inserted record increased database round-trips, network latency, and transaction duration.
What changed
iDempiere now supports the INSERT INTO ... RETURNING * SQL clause for database dialects that provide it, such as PostgreSQL.
When inserting a new PO record without explicitly specified ID values:
- The persistence engine appends
RETURNING *to the insert SQL statement. - The statement executes through
DB.executeInsertReturning(...), returning aResultSetwith the newly created row. - The
POinstance populates its internal fields directly from thatResultSetusingload(rs). - For tables without LOB columns, the subsequent
SELECTquery (load(m_trxName)) is completely bypassed. - If the record ID is already populated by the returned row, the additional UUID lookup query is also omitted.
If the active database dialect does not support RETURNING * (such as Oracle), PO automatically falls back to the previous DB.executeUpdate(...) and load(m_trxName) sequence.
This ticket also includes related concurrency and performance improvements:
- Table caching: Added a table name cache (
s_cacheByName) inMTableand removed class-level synchronization locks fromMTable.get(...). - Index lookup caching: Added
MTableIndex.get(Properties, int, String)to cache unique constraint index lookups. - Recent item concurrency: Made
m_labelinMRecentItemvolatile and removed synchronized locks fromclearLabel. - Workflow caching: Cached document value workflows in
MWorkflow.getDocValueusing an unmodifiable map without synchronization blocks. - Changelog configuration lookup: Moved the
SYSTEM_INSERT_CHANGELOGsystem configuration lookup outside the column processing loop inPO.saveNew().
How to set it up
No manual setup or database migration is required.
The feature activates automatically when iDempiere connects to a PostgreSQL database. On databases without RETURNING * support, the previous save and reload mechanism continues to operate without changes.
If a specific custom table or model cannot use RETURNING * (for example, a table with custom triggers, rules, or views that restrict RETURNING), developers can disable it for that model by overriding is_UseInsertReturning().
Technical notes
The core persistence and database abstraction layers introduce the following methods:
Database dialect capability
The org.compiere.db.AdempiereDatabase interface adds:
public default boolean isInsertReturningSupported() {
return false;
}
DB_PostgreSQL overrides this method to return true. DB_Oracle returns false.
Database helper methods
The org.compiere.util.DB class adds:
public static boolean isInsertReturningSupported();
public static boolean executeInsertReturning(
String sql,
Object[] params,
String trxName,
Function<ResultSet, Boolean> loader
);
public static boolean executeInsertReturning(
String sql,
Object[] params,
String trxName,
Function<ResultSet, Boolean> loader,
int timeOut
);
The method executes the insert statement as a query using CPreparedStatement.executeQuery(), advances to the first returned row (rs.next()), and passes the result set to the provided loader function.
Model extension point
The org.compiere.model.PO class adds:
protected boolean is_UseInsertReturning() {
return DB.isInsertReturningSupported();
}
Custom model classes can override is_UseInsertReturning() to return false if RETURNING * must be disabled for that specific table.
Migration impact
None. Existing models and plug-ins remain backward compatible. Databases that do not support INSERT INTO ... RETURNING * continue to use the standard insert and reload sequence.