Skip to content

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

  • PO is the foundation of ADempiere persistence.
  • Business models inherit persistence behavior instead of implementing it.
  • save() uses lifecycle hooks.
  • saveEx() provides exception-based persistence.
  • Trx manages transactions.
  • DB abstracts 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.