KB-212 – Accounting Engine Architecture¶
Knowledge Base ID: KB-212
Chapter: 13 - The PO Persistence Framework
Project: BLACK ERP
Version: 1.3
Status: Draft
Last Updated: 2026-06-26
Applies To: ADempiere 3.9.4
Purpose¶
This chapter explains the persistence framework used internally by ADempiere.
Previous chapters focused on the Accounting Engine and the posting process.
This chapter introduces the generic persistence architecture shared by almost every business object in the system.
Understanding this framework is essential because accounting objects such as MAccount, MInvoice, MPayment, MAllocationHdr, MProduct and hundreds of other models ultimately rely on the same persistence mechanism.
The Core Persistence Architecture¶
Almost every persistent business object in ADempiere follows the same inheritance hierarchy.
Database Table
↓
X_* Generated Model
↓
M_* Business Model
↓
PO
↓
Persistence Framework
Examples:
MInvoice
↓
X_C_Invoice
↓
PO
MPayment
↓
X_C_Payment
↓
PO
MAccount
↓
X_C_ValidCombination
↓
PO
The persistence behavior is inherited from PO.
Business models rarely implement their own persistence logic.
Responsibilities of PO¶
The PO class represents the generic persistence layer of ADempiere.
Its responsibilities include:
- Object persistence
- INSERT generation
- UPDATE generation
- DELETE support
- Transaction participation
- Change Log generation
- Validation hooks
- Savepoints
- Automatic reload
- Database independence
Every persistent model benefits from these services.
The save() Lifecycle¶
The central method of the framework is:
save()
Conceptually:
Business Object
↓
save()
↓
Validation
↓
Persistence
↓
Commit
↓
Return
Business models rarely override save() itself.
Instead, they participate through lifecycle hooks.
Lifecycle Hooks¶
The framework exposes several extension points.
beforeSave()
↓
save()
↓
afterSave()
Additional hooks include:
beforeDelete()
↓
delete()
↓
afterDelete()
This follows the Template Method design pattern.
The persistence algorithm remains centralized while individual models provide custom business rules.
save() vs saveEx()¶
The framework provides two persistence APIs.
save()
Returns:
boolean
Example:
if (!invoice.save())
{
...
}
The second API is:
saveEx()
This method throws an exception when persistence fails.
Example:
invoice.saveEx();
The exception-based approach generally produces cleaner application code.
Automatic Transaction Resolution¶
One of the most important discoveries during the source code review is that PO automatically determines whether it already belongs to a transaction.
Conceptually:
save()
↓
Existing Transaction?
↓
YES
↓
Use Savepoint
or
save()
↓
No Transaction
↓
Create Local Transaction
This behavior is transparent to business models.
Local Transactions¶
When no transaction exists:
save()
↓
Trx.createTrxName()
↓
Trx.get()
↓
Create Connection
↓
Persist
↓
Commit
↓
Close
The object completely manages its own transaction.
Existing Transactions¶
When the object already participates in a transaction:
save()
↓
Existing Trx
↓
Create Savepoint
↓
Persist
↓
Release Savepoint
If persistence fails:
Rollback Savepoint
The parent transaction remains active.
This behavior allows multiple business objects to participate safely within a larger business transaction.
The Role of Trx¶
Trx is the transaction manager of ADempiere.
Its primary responsibilities include:
- Transaction creation
- Connection management
- Commit
- Rollback
- Savepoints
- Connection lifecycle
Conceptually:
PO
↓
Trx
↓
Connection
↓
Database
The persistence framework never manipulates JDBC transactions directly.
It delegates all transaction control to Trx.
Connection Management¶
During the source code analysis we confirmed that Trx creates database connections through:
DB.createConnection()
The connection is immediately configured as:
AutoCommit = false
This guarantees transactional behavior throughout the persistence process.
Savepoints¶
One of the most advanced features of the framework is Savepoint support.
Conceptually:
Main Transaction
↓
Savepoint
↓
Persist Object
↓
Success
↓
Release Savepoint
If persistence fails:
Rollback Savepoint
rather than rolling back the complete business transaction.
This allows complex posting operations to remain consistent while minimizing unnecessary rollbacks.
The Role of DB¶
The DB utility class acts as the SQL abstraction layer.
Responsibilities include:
- Connection creation
- PreparedStatement creation
- SQL execution
- Transaction delegation
- Database abstraction
Typical operations include:
DB.executeUpdate()
DB.executeUpdateEx()
DB.prepareStatement()
DB.commit()
DB.rollback()
Business models rarely execute JDBC directly.
Instead, they use the centralized DB utilities.
Automatic Reload¶
After a successful INSERT or UPDATE, the persistence framework reloads the object.
Conceptually:
INSERT
↓
Reload
↓
Updated Object
This ensures that generated values such as:
- Primary Key
- UUID
- Document Number
- Audit Columns
are immediately available to the application.
Change Log Integration¶
The persistence framework integrates with ADempiere's auditing system.
When enabled, persistence automatically generates entries inside:
AD_ChangeLog
This functionality is completely transparent to business models.
Relationship with the Accounting Engine¶
The Accounting Engine does not implement its own persistence.
Instead, it relies on the generic persistence framework.
Example:
MAccount
↓
PO.save()
↓
Trx
↓
DB
↓
C_ValidCombination
The same persistence architecture is reused throughout the entire ERP.
BLACK ERP Example¶
During the Mexican VAT Cash Basis implementation, additional accounting combinations were resolved through MAccount.
Whenever a new valid accounting combination is required:
MAccount
↓
save()
↓
PO
↓
Trx
↓
DB
↓
C_ValidCombination
The localization therefore inherits every transactional guarantee provided by the framework without implementing custom persistence logic.
Engineering Principles¶
The source code demonstrates several important architectural principles.
- Persistence is centralized.
- Business models remain lightweight.
- Transactions are transparent.
- Savepoints protect larger transactions.
- Database access is abstracted.
- Lifecycle hooks enable safe customization.
- Accounting objects use the same persistence framework as every other business object.
Key Takeaways¶
POis the foundation of ADempiere persistence.- Business models inherit persistence behavior instead of implementing it.
save()uses lifecycle hooks.saveEx()provides exception-based persistence.Trxmanages transactions.DBabstracts SQL execution.- Savepoints protect nested persistence operations.
- The Accounting Engine reuses the generic persistence framework.
Next Chapter¶
Chapter 14 explores the complete transaction flow from business objects to the database engine, including nested transactions, isolation, connection reuse and persistence performance.
Revision History¶
| Version | Date | Description |
|---|---|---|
| 1.3 | 2026-06-26 | Added complete PO Persistence Framework based on source code analysis of PO.java, Trx.java, DB.java and MAccount.java. |