Skip to content

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().
  • PO validates objects before persistence.
  • SQL generation is centralized.
  • DB abstracts JDBC.
  • PostgreSQL executes prepared statements.
  • Trx manages 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.