KB-212 – Accounting Engine Architecture¶
Knowledge Base ID: KB-212
Chapter: 14 - Transaction Flow from PO to PostgreSQL
Project: BLACK ERP
Version: 1.4
Status: Draft
Last Updated: 2026-06-26
Applies To: ADempiere 3.9.4
Purpose¶
This chapter explains the complete execution path followed by every persistent business object after invoking save().
Rather than focusing on the Accounting Engine itself, this chapter follows the underlying persistence flow that ultimately stores data inside PostgreSQL.
Understanding this execution path is fundamental for debugging persistence issues, performance problems and transaction behavior.
Overview¶
Previous chapters introduced:
- PO
- Trx
- DB
- MAccount
- C_ValidCombination
This chapter connects those components into one complete execution pipeline.
Conceptually:
Business Object
↓
PO.save()
↓
PO.saveNew()
↓
DB.executeUpdate()
↓
JDBC
↓
PostgreSQL
Every persistent object in ADempiere follows this architecture.
High-Level Transaction Flow¶
Business Object
↓
save()
↓
beforeSave()
↓
Validation
↓
Generate SQL
↓
DB.executeUpdate()
↓
JDBC PreparedStatement
↓
PostgreSQL
↓
Commit
↓
afterSave()
This sequence applies regardless of the specific business model.
Step 1 — Business Object¶
Every persistent model eventually calls:
save()
Examples include:
- MInvoice
- MPayment
- MAccount
- MBPartner
- MProduct
- MAllocationHdr
The persistence framework begins here.
Step 2 — Validation¶
Before generating SQL, PO performs several validations.
Typical checks include:
- Mandatory fields
- Data type validation
- Client consistency
- Organization consistency
- Custom
beforeSave()logic
If validation fails, no SQL is executed.
Step 3 — SQL Generation¶
After validation succeeds, the persistence framework builds the SQL statement.
Depending on the object state:
INSERT
or
UPDATE
The business model never constructs SQL manually.
This responsibility belongs entirely to PO.
Step 4 — DB Utility Layer¶
The generated SQL is delegated to:
DB.executeUpdate()
or
DB.executeUpdateEx()
The DB utility abstracts all JDBC interactions.
Responsibilities include:
- PreparedStatement creation
- Parameter binding
- Exception translation
- Transaction participation
Step 5 — JDBC Layer¶
The DB utility creates a JDBC PreparedStatement.
Conceptually:
SQL
↓
PreparedStatement
↓
Bind Parameters
↓
Execute
Prepared statements improve both security and performance.
Step 6 — PostgreSQL Execution¶
The database receives the prepared SQL statement.
Typical operations include:
INSERT INTO C_Invoice
UPDATE C_Payment
INSERT INTO FACT_ACCT
UPDATE C_ValidCombination
At this stage, the database engine performs:
- Constraint validation
- Foreign key validation
- Trigger execution
- Index maintenance
- WAL logging
Step 7 — Commit¶
If the transaction succeeds:
Commit
The changes become permanently visible.
If an error occurs:
Rollback
No partial persistence remains.
Nested Transactions¶
One of the strengths of the framework is support for nested persistence.
Conceptually:
Invoice.save()
↓
Payment.save()
↓
Allocation.save()
↓
Commit
Each object participates in the same transaction through Trx.
No object commits independently.
Savepoint Behavior¶
When persistence occurs inside an existing transaction:
Main Transaction
↓
Savepoint
↓
Persist Object
↓
Release Savepoint
If an exception occurs:
Rollback Savepoint
The outer transaction continues safely.
Connection Reuse¶
The framework minimizes database overhead by reusing the same JDBC connection throughout the transaction.
Conceptually:
PO
↓
Trx
↓
Single JDBC Connection
↓
Multiple SQL Statements
This avoids unnecessary connection creation during complex business operations.
Relationship with the Accounting Engine¶
The Accounting Engine relies entirely on this persistence infrastructure.
Example:
Fact
↓
FactLine
↓
PO.save()
↓
DB
↓
PostgreSQL
Accounting posting therefore inherits all transactional guarantees provided by the framework.
BLACK ERP Example¶
During the Mexican VAT Cash Basis implementation, additional VAT accounting entries were generated inside the Allocation posting process.
Each additional accounting object followed exactly the same persistence path:
MXVATCashBasisEngine
↓
FactLine
↓
PO.save()
↓
DB.executeUpdate()
↓
PostgreSQL
No custom persistence logic was required.
Performance Considerations¶
The architecture provides several performance advantages.
- Prepared Statements
- Connection reuse
- Transaction batching
- Savepoint support
- Centralized SQL generation
- Reduced JDBC boilerplate
These optimizations benefit every business object automatically.
Engineering Principles¶
The source code reveals several important design decisions.
- Business models never execute SQL directly.
- Persistence is centralized.
- JDBC is abstracted.
- Transactions are shared.
- Savepoints protect nested operations.
- PostgreSQL interaction is isolated behind the DB utility layer.
Key Takeaways¶
- Every persistent object ultimately calls
PO.save(). POvalidates objects before persistence.- SQL generation is centralized.
DBabstracts JDBC.- PostgreSQL executes prepared statements.
Trxmanages transaction scope.- Nested transactions use savepoints.
- The Accounting Engine inherits the same persistence architecture.
Next Chapter¶
Chapter 15 explores the internal caching architecture of ADempiere, including CCache, object reuse, lazy loading and performance optimization strategies used throughout the Core framework.
Revision History¶
| Version | Date | Description |
|---|---|---|
| 1.4 | 2026-06-26 | Added complete persistence execution flow from PO.save() through DB, JDBC and PostgreSQL. |