Showing posts with label #BackendDevelopment. Show all posts
Showing posts with label #BackendDevelopment. Show all posts

Tuesday, November 10, 2026

SAP Commerce Transaction Management Explained: @Transactional, Rollback, TransactionTemplate, ModelService and Real-World Database Issues

 

Introduction

Imagine you are developing an SAP Commerce service that performs the following operations:

  1. Creates a custom inbound order.

  2. Creates multiple inbound order lines.

  3. Updates the order status.

  4. Saves all the models.

  5. Calls an external ERP API.

Now imagine that the third operation succeeds, but the fourth operation throws an exception.

What happens to the models already saved?

Will all the changes be rolled back? Does ModelService.save() immediately commit the transaction? What if one method calls another method annotated with @Transactional? What if the ERP API succeeds but the database transaction subsequently fails?

These are not theoretical questions. They are common problems in enterprise SAP Commerce implementations.

Transaction management is the mechanism that controls how a group of database operations succeeds or fails as a unit.

In this article, we will explore transaction management from the Java and SAP Commerce perspectives, including practical code examples, common mistakes, and production scenarios.

1. What is a database transaction?

A database transaction is a logical group of operations that should be treated as one unit of work.

Consider an order-processing operation:

Start Transaction
       |
       v
Create Order
       |
       v
Create Order Entries
       |
       v
Update Order Status
       |
       v
Save Changes
       |
       v
Commit Transaction

If an operation fails before the transaction commits, the database changes can be rolled back according to the transaction manager's rules.

For example:

Start Transaction
       |
       v
Create Order
       |
       v
Create Order Entries
       |
       v
Update Order Status
       |
       v
Exception Occurred
       |
       v
Rollback Transaction
       |
       v
Database Changes Reverted

This is the foundation of reliable data processing.

The ACID properties

Database transactions are commonly described through four properties called ACID.

PropertyMeaningSAP Commerce example
AtomicityAll operations succeed or fail togetherOrder and its entries are persisted together
ConsistencyData remains within defined integrity rulesRequired attributes and database constraints are respected
IsolationConcurrent transactions do not interfere beyond the configured isolation guaranteesTwo requests updating the same business record
DurabilityCommitted changes survive a database failure within the database's durability guaranteesA committed order remains persisted

Atomicity

Suppose a custom service creates an order and five order entries.

If entry number four fails, atomicity helps prevent a partially persisted order from being left behind, provided the operations participate in the same transaction.

Consistency

A transaction must preserve the integrity rules of the application and database.

For example, an order should not be persisted in a state that violates a required database constraint.

Isolation

Suppose two requests try to update the same inventory record simultaneously.

Transaction isolation controls what one transaction can observe while another transaction is running.

Durability

Once the transaction has successfully committed, the database is responsible for preserving the committed changes.

2. How transaction management works in SAP Commerce

SAP Commerce uses Spring-based service-layer architecture and transaction infrastructure.

A simplified flow looks like this:

OCC Controller
      |
      v
Facade
      |
      v
Service Layer
      |
      v
Transactional Boundary
      |
      v
ModelService
      |
      v
Persistence Layer
      |
      v
Database

A typical business operation may involve multiple services and model operations.

For example:

public void processInboundOrder()
{
    createInboundOrder();
    createInboundOrderLines();
    updateOrderStatus();
}

If these operations participate in the same transaction, an exception can cause the complete database operation to roll back.

However, calling three methods from one Java method does not automatically guarantee that they participate in one transaction.

The transaction boundary depends on the configured transaction manager, Spring transaction interception, existing transaction context, and the behavior of the operations being invoked.

This distinction is important when debugging SAP Commerce persistence problems.

3. Understanding @Transactional in SAP Commerce

Spring provides declarative transaction management through the @Transactional annotation.

Instead of manually starting and committing a transaction, developers can declare that a method should execute transactionally.

Example:

import org.springframework.transaction.annotation.Transactional;

@Transactional
public void processOrder()
{
    createOrder();
    createOrderEntries();
    updateOrderStatus();
}

When the method is invoked through the configured Spring transaction proxy, the transaction infrastructure applies the configured transaction behavior.

Conceptually:

Method Invocation
       |
       v
Spring Transaction Proxy
       |
       v
Start or Join Transaction
       |
       v
Execute Method
       |
       +---- Success ----> Commit
       |
       +---- Failure ----> Rollback (according to rules)

The transaction proxy is responsible for applying the transaction behavior around the method invocation.

Where should @Transactional be used?

In a typical SAP Commerce application, the service layer is a suitable place to define transaction boundaries for business operations.

Example:

public class CustomInboundOrderService
{
    @Transactional
    public void processInboundOrder()
    {
        // Create inbound order
        // Create inbound lines
        // Update processing status
    }
}

The actual transaction configuration and proxy setup must be verified in the extension and platform configuration.

Important: @Transactional does not work in every calling situation

Consider this example:

public class CustomOrderService
{
    public void processOrder()
    {
        saveOrder();
    }

    @Transactional
    public void saveOrder()
    {
        // Database operations
    }
}

A developer may expect saveOrder() to start a transaction.

However, Spring's standard proxy-based transaction management does not intercept ordinary self-invocation. Here, processOrder() calls saveOrder() directly on the same object, bypassing the proxy.

Therefore, the transactional annotation may not be applied as expected.

Recommended approach

Move the transactional operation into a separate Spring-managed service:

public class CustomOrderService
{
    private CustomOrderPersistenceService persistenceService;

    public void processOrder()
    {
        persistenceService.saveOrder();
    }

    public void setPersistenceService(
            final CustomOrderPersistenceService persistenceService)
    {
        this.persistenceService = persistenceService;
    }
}

The second service:

public class CustomOrderPersistenceService
{
    @Transactional
    public void saveOrder()
    {
        // Database operations
    }
}

Now the call goes through the Spring-managed proxy, assuming the bean is correctly configured.

Interview point: @Transactional is not simply a Java keyword. It relies on Spring transaction infrastructure and method interception.

4. Understanding commit and rollback

Two of the most important transaction operations are:

  • Commit: permanently completes the transaction's database changes.

  • Rollback: discards uncommitted changes made within the transaction.

Consider this example:

@Transactional
public void createCustomer()
{
    final CustomerModel customer = modelService.create(CustomerModel.class);

    customer.setUid("customer001");

    modelService.save(customer);

    throw new IllegalStateException("Something went wrong");
}

Assuming this method is actually executing inside the expected Spring-managed transaction, and no other transaction behavior overrides the rollback rules, the unchecked exception normally causes the transaction to roll back.

The customer creation should therefore not remain committed as part of that transaction.

What happens if the exception is caught?

Consider:

@Transactional
public void createCustomer()
{
    try
    {
        final CustomerModel customer =
                modelService.create(CustomerModel.class);

        customer.setUid("customer001");

        modelService.save(customer);

        throw new IllegalStateException("Processing failed");
    }
    catch (Exception exception)
    {
        LOG.error("Error occurred", exception);
    }
}

This is a common source of confusion.

The exception is caught and does not escape the transactional method. Under normal Spring rollback rules, the transaction may therefore complete successfully and commit.

Catching an exception does not automatically mean the transaction will roll back.

If the operation must roll back, you need to use an appropriate rollback strategy.

For example, you can rethrow the exception:

@Transactional
public void createCustomer()
{
    try
    {
        // Database operations
    }
    catch (Exception exception)
    {
        LOG.error("Customer creation failed", exception);
        throw exception;
    }
}

If you cannot rethrow the exception, you can explicitly mark the transaction rollback-only using Spring transaction support where appropriate:

import org.springframework.transaction.interceptor
        .TransactionAspectSupport;

TransactionAspectSupport.currentTransactionStatus()
        .setRollbackOnly();

Use this only when executing within a transaction managed by the relevant Spring transaction infrastructure.

5. Checked exceptions versus unchecked exceptions

This is an important Java and Spring transaction concept.

By default, Spring rolls back transactions when an unchecked exception (RuntimeException) or an Error escapes the transactional method.

Checked exceptions do not trigger rollback by default.

Example:

@Transactional
public void processOrder() throws Exception
{
    createOrder();

    throw new Exception("Order processing failed");
}

A developer might expect the order to be rolled back.

However, with Spring's default rollback rules, a checked exception does not automatically trigger rollback.

Solution: rollbackFor

You can explicitly configure the rollback rule:

@Transactional(rollbackFor = Exception.class)
public void processOrder() throws Exception
{
    createOrder();

    throw new Exception("Order processing failed");
}

Now the configured checked exception is included in the rollback rules.

You can also configure specific exceptions:

@Transactional(
    rollbackFor = {
        Exception.class,
        CustomIntegrationException.class
    }
)
public void processOrder() throws Exception
{
    // Business logic
}

Choose rollback rules carefully. Some business exceptions may represent expected outcomes that should not roll back all database changes.

The correct behavior depends on the business requirement.

6. Understanding transaction propagation

Transaction propagation determines what happens when a transactional method calls another transactional method.

This is one of the most important concepts for experienced Java and SAP Commerce developers.

Spring provides several propagation options.

6.1 REQUIRED

REQUIRED is the default propagation behavior.

It means:

  • If a transaction already exists, join it.

  • If no transaction exists, create a new one.

Example:

@Transactional
public void processOrder()
{
    orderService.saveOrder();
    paymentService.savePayment();
}

If all three operations use the same transaction manager and compatible transaction settings, the called transactional methods normally participate in the existing transaction.

processOrder()
      |
      v
Transaction A
      |
      +---- saveOrder()
      |
      +---- savePayment()
      |
      v
Commit Transaction A

If an exception causes the shared transaction to be marked rollback-only, the transaction may roll back even if an inner method catches the original exception.

6.2 REQUIRES_NEW

REQUIRES_NEW suspends an existing transaction and executes the method within a separate transaction.

Example:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditRecord()
{
    // Save audit record
}

Imagine:

Main Transaction A
       |
       v
Save Order
       |
       v
Call Audit Service
       |
       v
Suspend Transaction A
       |
       v
Start Transaction B
       |
       v
Save Audit Record
       |
       v
Commit Transaction B
       |
       v
Resume Transaction A
       |
       v
Main Transaction Fails
       |
       v
Rollback Transaction A

The audit record may remain committed even though the main transaction rolls back.

This can be useful when certain records must survive the failure of the main operation.

However, use it carefully.

It does not make the entire business operation atomic. It creates an independent commit boundary, and it can introduce additional database connections and locking pressure.

Also, an independent transaction cannot necessarily read uncommitted changes from the suspended transaction.

6.3 SUPPORTS

SUPPORTS means:

  • Join an existing transaction if one exists.

  • Otherwise execute without a transaction.

Useful when a method can work in either context and does not require an independent transaction.

6.4 MANDATORY

MANDATORY requires an existing transaction.

If no transaction exists, the call fails.

This can help enforce an architectural rule that a method must always execute inside a caller's transaction.

6.5 NOT_SUPPORTED

NOT_SUPPORTED suspends an existing transaction and executes without a transaction.

It may be useful for operations that should not hold database locks for a long time, but should not be used to bypass required transactional guarantees.

6.6 NEVER

NEVER requires that no transaction exists. If one is active, the call fails.

Propagation summary

PropagationExisting transactionNo existing transaction
REQUIREDJoins itCreates one
REQUIRES_NEWSuspends it and creates anotherCreates one
SUPPORTSJoins itNo transaction
MANDATORYJoins itThrows an exception
NOT_SUPPORTEDSuspends itNo transaction
NEVERThrows an exceptionNo transaction

The propagation behavior is defined by Spring. Confirm that the configured SAP Commerce transaction manager and the method invocation path support the behavior you intend.

7. ModelService.save() and transaction management

This is particularly important in SAP Commerce.

Many developers assume that:

modelService.save(model);

always means that the model has been committed to the database.

That assumption is not universally correct.

ModelService is the central service-layer API for creating, loading, saving, and removing models. Its transaction behavior is also influenced by the platform configuration.

The model.service.transactional.saves property controls the global setting for transactional model saves. ModelService also provides:

  • enableTransactions()

  • disableTransactions()

  • clearTransactionsSettings()

These methods influence the transaction behavior of save operations through session-level settings. See the SAP Commerce ModelService API documentation.

Example

final CustomerModel customer =
        modelService.create(CustomerModel.class);

customer.setUid("customer001");

modelService.save(customer);

This saves the model through the ServiceLayer persistence mechanism.

But whether the save uses its own transaction or participates in an existing transaction depends on the configured behavior.

Do not assume that every call to save() creates an independent commit.

Enabling transactional saves

SAP Commerce exposes:

modelService.enableTransactions();

And:

modelService.disableTransactions();

These override the global setting for model save operations.

The settings are session-based. SAP recommends using a local session view when applying these settings temporarily and clearing the settings when necessary.

Illustrative example:

sessionService.executeInLocalView(
    new SessionExecutionBody()
    {
        @Override
        public Object execute()
        {
            modelService.enableTransactions();

            modelService.save(customer);

            return customer;
        }
    }
);

Do not apply these methods indiscriminately to every save operation. First understand the global configuration and the transaction boundary of the calling service.

Important distinction

There are two related but different questions:

  1. Should this model save operation use transactional behavior?

  2. Should several business operations participate in one larger transaction?

The ModelService transaction settings concern save operations. A Spring-managed transaction around a business service defines a broader transaction boundary.

Understanding the distinction helps avoid partial persistence and unexpected commits.

8. TransactionTemplate: programmatic transaction management

Sometimes declarative transaction management is not the best fit.

For example, you may need to execute a transaction conditionally, or you may want the transaction boundary to be very explicit in a particular section of code.

Spring provides TransactionTemplate for programmatic transaction management.

SAP's ServiceLayer migration documentation also recommends using TransactionTemplate instead of manually managing transactions through the legacy Jalo Transaction class. See Transitioning to the ServiceLayer.

Spring configuration

<bean id="customTransactionTemplate"
      class="org.springframework.transaction.support.TransactionTemplate">

    <property name="transactionManager" ref="txManager"/>

</bean>

Use the transaction manager configured for the relevant SAP Commerce persistence operations in your project.

Java implementation

import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

public class CustomInboundOrderService
{
    private TransactionTemplate transactionTemplate;

    public void processInboundOrder()
    {
        transactionTemplate.execute(
            new TransactionCallback<Void>()
            {
                @Override
                public Void doInTransaction(
                        final TransactionStatus status)
                {
                    createInboundOrder();
                    createInboundOrderLines();
                    updateInboundOrderStatus();

                    return null;
                }
            }
        );
    }

    public void setTransactionTemplate(
            final TransactionTemplate transactionTemplate)
    {
        this.transactionTemplate = transactionTemplate;
    }
}

The transaction template handles the transaction lifecycle around the callback.

An unchecked exception escaping the callback normally causes rollback according to the configured transaction rules.

When should you use TransactionTemplate?

  • When the transaction boundary needs to be explicit in the code.

  • When only a particular block should execute transactionally.

  • When transaction behavior needs to be selected programmatically.

  • When replacing legacy manual transaction handling.

For ordinary service-layer business operations, @Transactional is often simpler and easier to maintain.

9. Transaction isolation levels

Transaction isolation controls how concurrent transactions interact.

Consider two users trying to update the same stock record.

Transaction A reads stock as 10.

Transaction B also reads stock as 10.

Transaction A reduces stock by 2.

Transaction B reduces stock by 3.

Without appropriate concurrency protection, the final result may not reflect the intended business operation.

This is why transaction isolation matters.

Common database isolation levels include:

Isolation levelGeneral behavior
READ_UNCOMMITTEDMay allow dirty reads
READ_COMMITTEDPrevents dirty reads
REPEATABLE_READPrevents non-repeatable reads
SERIALIZABLEProvides the strongest standard isolation guarantees

The actual behavior and performance depend on the database implementation.

Dirty read

Transaction A updates a record but has not committed.

Transaction B reads the uncommitted value.

If Transaction A rolls back, Transaction B has read data that never became committed.

Non-repeatable read

Transaction A reads a record.

Transaction B updates and commits it.

Transaction A reads the same record again and sees a different value.

Phantom read

Transaction A queries records matching a condition.

Transaction B inserts another matching record and commits.

When Transaction A repeats the query, it may see an additional record.

SAP Commerce consideration

Do not change the database isolation level simply because a concurrency issue appears.

First identify the actual problem:

  • Is it a lost update?

  • Is it a stale model?

  • Is it a database lock?

  • Is it a unique-key conflict?

  • Is it a transaction timeout?

  • Is it an optimistic locking or version conflict?

Then select the appropriate protection mechanism.

10. Concurrency, locking, and ModelService.lock()

Transaction isolation is not the only way to protect business data.

SAP Commerce ModelService provides a lock(PK) method for locking an item for changes.

For example:

modelService.lock(order.getPk());

try
{
    final OrderModel lockedOrder =
            modelService.get(order.getPk());

    lockedOrder.setStatus(OrderStatus.PROCESSING);

    modelService.save(lockedOrder);
}
finally
{
    // Complete the operation within the surrounding
    // transaction and locking lifecycle.
}

This is an illustrative pattern, not a complete locking implementation for every SAP Commerce version or database configuration.

The locking mechanism and transaction lifecycle must be understood before using it.

Example: Two requests update the same order

Request A                 Request B
    |                         |
    v                         v
Read Order                Read Order
    |                         |
    v                         v
Acquire Lock              Wait for Lock
    |
    v
Update Order
    |
    v
Save Order
    |
    v
Commit
    |
    +-------------------------+
                              |
                              v
                         Acquire Lock
                              |
                              v
                         Read Current State
                              |
                              v
                         Apply Valid Update

A lock can help serialize changes to the same record.

However, holding locks for long periods can reduce throughput and increase deadlock risk.

Best practice: Keep transactions short and lock only what is necessary.

11. Database deadlocks: a real-world scenario

A deadlock occurs when two or more transactions wait for resources held by each other, and none can proceed.

Consider two transactions:

Transaction A              Transaction B
      |                          |
      v                          v
Lock Record 1               Lock Record 2
      |                          |
      v                          v
Request Record 2            Request Record 1
      |                          |
      v                          v
Wait for B                  Wait for A

Both transactions are waiting.

The database detects the deadlock and typically aborts one of the transactions so the other can continue.

Common causes

  • Locking records in different orders.

  • Long-running transactions.

  • Updating the same records concurrently.

  • Performing external API calls while holding database locks.

  • Processing large collections inside one transaction.

  • Unnecessary nested transaction boundaries.

How to reduce deadlocks

  1. Keep transaction duration short.

  2. Acquire locks in a consistent order.

  3. Avoid unnecessary database updates.

  4. Avoid external network calls inside long-running transactions.

  5. Process large datasets in controlled batches.

  6. Retry only when the failure is known to be transient and the operation is safe to repeat.

  7. Inspect database deadlock logs and execution plans.

Do not simply increase transaction timeouts to hide deadlocks. A timeout does not solve the underlying locking conflict.

12. External API calls inside database transactions

This is one of the most important design mistakes in enterprise integration development.

Consider:

@Transactional
public void processOrder()
{
    updateOrderStatus();

    paymentClient.authorizePayment();

    updatePaymentStatus();

    modelService.save(order);
}

The payment API might take 10 seconds to respond.

During that time, the database transaction may remain open and hold resources or locks.

Worse, the payment provider may successfully authorize the payment, but the database transaction may fail afterward.

What happens?

Start Database Transaction
          |
          v
Update Order
          |
          v
Call Payment API
          |
          v
Payment Successfully Authorized
          |
          v
Database Exception
          |
          v
Rollback Database Transaction

The database rolls back its own changes. It cannot automatically reverse the external payment authorization.

This creates an inconsistency between systems.

Better design

Separate database work from external integration where possible.

Transaction A
     |
     v
Create Payment Request
     |
     v
Commit
     |
     v
Call Payment Provider
     |
     v
Receive Result
     |
     v
Transaction B
     |
     v
Update Payment Status
     |
     v
Commit

This approach still requires reliable coordination and recovery.

For critical operations, use an idempotency key, persist the request identifier, and reconcile uncertain outcomes.

For more complex workflows, consider an outbox-style integration pattern or a business process that coordinates the stages.

Remember: A database transaction cannot provide atomicity across an unrelated external HTTP API unless a specific distributed transaction protocol is supported by all participants. Ordinary REST calls do not become transactional merely because they occur inside an @Transactional method.

13. Batch processing and transaction boundaries

Suppose you need to process 50,000 inbound records.

A naive implementation might look like:

@Transactional
public void processAllRecords(List<InboundRecord> records)
{
    for (InboundRecord record : records)
    {
        processRecord(record);
    }
}

This creates a potentially large transaction.

Possible problems:

  • Long transaction duration.

  • Increased lock contention.

  • High memory usage.

  • More difficult recovery.

  • Large rollback cost if a late record fails.

  • Increased pressure on the database.

Better approach: process in batches

For example:

Total Records: 50,000

Batch 1: 500 records
      |
      v
Process and Commit

Batch 2: 500 records
      |
      v
Process and Commit

Batch 3: 500 records
      |
      v
Process and Commit

...

The exact batch size depends on the data model, database, workload, and performance testing.

A practical implementation can delegate each batch to a separate Spring-managed service method:

public void processRecords(List<InboundRecord> records)
{
    final int batchSize = 500;

    for (int start = 0; start < records.size(); start += batchSize)
    {
        final int end = Math.min(start + batchSize, records.size());

        final List<InboundRecord> batch =
                records.subList(start, end);

        batchProcessingService.processBatch(batch);
    }
}

The batch service:

public class BatchProcessingService
{
    @Transactional
    public void processBatch(List<InboundRecord> records)
    {
        for (InboundRecord record : records)
        {
            processRecord(record);
        }
    }
}

The separate service bean is important: the call must go through the Spring proxy for the transactional boundary to be applied.

With this structure, each batch can commit independently.

If batch four fails, previously committed batches are not automatically rolled back.

That means the processing design must support restarting from the last successful batch and safely handling records that might be encountered again.

14. Common transaction-related mistakes in SAP Commerce

Here are common mistakes that can lead to inconsistent data or difficult production issues.

MistakeWhy it is a problemBetter approach
Assuming every save() commits independentlySave behavior depends on transaction configuration and contextUnderstand the transaction boundary
Catching exceptions without considering rollbackThe transaction may commit unexpectedly or already be rollback-onlyDefine explicit rollback behavior
Using @Transactional through self-invocationSpring proxy interception may be bypassedCall through another Spring bean
Making external API calls inside long transactionsHolds database resources and cannot roll back external effectsSeparate integration and persistence stages
Processing thousands of records in one transactionIncreases locking and rollback costsUse controlled batches
Using REQUIRES_NEW everywhereCreates independent commit boundaries and can complicate consistencyUse only for genuine independent work
Manually managing legacy Jalo transactions in ServiceLayer codeCan conflict with the intended ServiceLayer transaction modelPrefer supported Spring transaction mechanisms
Ignoring duplicate requestsRetries may repeat business operationsApply idempotency and uniqueness controls

15. Real-world scenario: inbound order processing

Let's put these concepts together using an SAP Commerce integration example.

Assume an external ERP sends an inbound order containing multiple lines.

The processing requirement is:

  1. Create an inbound order.

  2. Create inbound order lines.

  3. Validate the records.

  4. Update the processing status.

  5. Send an acknowledgement to the ERP.

Recommended architecture

ERP Request
    |
    v
Inbound Controller
    |
    v
Inbound Order Facade
    |
    v
Inbound Order Service
    |
    v
Database Transaction
    |
    +---- Create Inbound Order
    |
    +---- Create Inbound Lines
    |
    +---- Validate Required Data
    |
    +---- Update Status
    |
    v
Commit Transaction
    |
    v
Integration / Acknowledgement
    |
    v
ERP

Example service implementation

public class InboundOrderService
{
    private ModelService modelService;

    @Transactional(rollbackFor = Exception.class)
    public void createInboundOrder(final InboundOrderData data)
            throws Exception
    {
        final CustSAPCpiInboundOrderModel order =
                modelService.create(CustSAPCpiInboundOrderModel.class);

        order.setOrderNumber(data.getOrderNumber());

        modelService.save(order);

        for (InboundOrderLineData lineData : data.getLines())
        {
            final CustSAPCpiInboundLineModel line =
                    modelService.create(
                            CustSAPCpiInboundLineModel.class);

            line.setLineNumber(lineData.getLineNumber());
            line.setInboundOrder(order);

            modelService.save(line);
        }

        order.setProcessingStatus("CREATED");

        modelService.save(order);
    }

    public void setModelService(final ModelService modelService)
    {
        this.modelService = modelService;
    }
}

This example assumes the custom item types, attributes, relation, and service configuration already exist.

The @Transactional annotation must be invoked through the configured Spring proxy. The model save behavior must also be consistent with the intended transaction boundary.

What if the fifth line fails?

If the exception escapes the transactional method and the operations participate in the same transaction, the transaction can roll back the order and earlier lines.

What if the ERP acknowledgement fails?

The database transaction may already have committed.

The acknowledgement should therefore have its own retry or reconciliation strategy. Re-running the entire order creation operation without idempotency protection could create duplicates.

This distinction is essential when designing robust inbound integrations.

16. How to troubleshoot transaction issues in production

When transaction behavior is unexpected, follow a structured investigation.

Step 1: Identify the transaction boundary

Find the service method where the operation starts.

Check:

  • Is it annotated with @Transactional?

  • Is it called through a Spring bean?

  • Is the method invocation intercepted by the transaction proxy?

  • Is another transaction already active?

Step 2: Check the exception type

Determine whether the exception is:

  • Checked exception.

  • Runtime exception.

  • Error.

  • Exception caught and swallowed inside the method.

Review the configured rollback rules.

Step 3: Check ModelService transaction settings

Review:

  • model.service.transactional.saves

  • Any calls to enableTransactions()

  • Any calls to disableTransactions()

  • Whether temporary session settings are cleared appropriately.

Step 4: Check nested service calls

Look for:

  • REQUIRED

  • REQUIRES_NEW

  • Self-invocation

  • Calls to different transaction managers

  • Calls executed asynchronously or in another thread

A new thread does not automatically inherit the original thread's transaction context.

Step 5: Investigate database errors

Look for:

  • Deadlock exceptions.

  • Lock timeouts.

  • Unique-key violations.

  • Transaction timeouts.

  • Connection pool exhaustion.

  • Database constraint violations.

Step 6: Verify actual persisted data

Do not rely only on the Java method's return value.

Verify the final state through supported SAP Commerce APIs or an appropriate read-only database investigation.

Check whether the transaction committed, rolled back, or partially committed across separate transaction boundaries.

17. Senior-level interview questions

Q1. What is the difference between ModelService.save() and a transaction commit?

ModelService.save() persists model changes through the ServiceLayer. The actual transaction and commit behavior depends on the transaction context and model-save configuration. A save call should not automatically be treated as an independent commit.

Q2. What happens when a RuntimeException is thrown from an @Transactional method?

Under Spring's default rollback rules, an unchecked exception escaping the transactional method triggers rollback, assuming the transaction is managed by the relevant Spring proxy and no custom rollback rule changes the behavior.

Q3. Why does @Transactional sometimes not work when one method calls another method in the same class?

Because Spring's standard proxy-based transaction management does not intercept ordinary self-invocation. The internal method call bypasses the proxy.

Q4. What is the difference between REQUIRED and REQUIRES_NEW?

REQUIRED joins an existing transaction or creates one if none exists. REQUIRES_NEW suspends the existing transaction and executes in an independent transaction.

Q5. Does catching an exception guarantee that a transaction will commit?

No. The transaction may already have been marked rollback-only by an inner operation. Conversely, catching an exception that would otherwise trigger rollback can result in a commit if no rollback-only condition was set.

Q6. Why should external REST calls generally be avoided inside long database transactions?

They can hold database resources while waiting for the network, and their side effects cannot be automatically undone by a database rollback.

Q7. What is a database deadlock?

A deadlock occurs when transactions wait for resources held by one another, preventing progress. The database usually detects and aborts one of the transactions.

Q8. When would you use TransactionTemplate instead of @Transactional?

When transaction execution needs to be explicitly controlled in a particular code block or selected programmatically. For normal service methods, declarative transactions are often simpler.

Q9. What is the difference between database isolation and locking?

Isolation defines how concurrent transactions interact and what data they can observe. Locking controls access to particular resources to coordinate concurrent modifications.

Q10. How would you process 100,000 inbound records safely?

Use controlled batches, independent transaction boundaries where appropriate, checkpoints, idempotent record processing, and a recovery mechanism. Avoid holding one transaction open for the entire dataset unless the business requirement genuinely demands it.

Q11. Can a database transaction roll back a successful payment API call?

No. A regular database transaction controls database resources, not an unrelated external REST API. The integration requires compensation, idempotency, or reconciliation.

Q12. What is the purpose of rollbackFor in Spring?

It explicitly configures which exception types should trigger rollback, including checked exceptions that do not trigger rollback under the default Spring rules.

18. Final best practices

Before implementing transaction management in SAP Commerce, remember these principles:

  1. Define transaction boundaries around meaningful business operations.

  2. Prefer Spring-managed transaction handling in ServiceLayer code.

  3. Understand the difference between a model save and a transaction commit.

  4. Avoid long-running database transactions.

  5. Use REQUIRES_NEW only when independent commit behavior is genuinely required.

  6. Understand Spring proxy behavior before relying on @Transactional.

  7. Configure rollback rules according to business requirements.

  8. Avoid external API calls inside long-running transactions.

  9. Use batch processing for large datasets.

  10. Investigate locks and deadlocks instead of simply increasing timeouts.

  11. Design integrations to handle retries and duplicate requests safely.

  12. Verify transaction behavior in the actual SAP Commerce version and configuration.

Conclusion

Transaction management is one of the most important foundations of a reliable SAP Commerce application.

Knowing how to use @Transactional is only the starting point. Experienced developers must also understand transaction propagation, rollback rules, model persistence behavior, isolation, locking, and the limitations of database transactions when external systems are involved.

When these concepts are applied correctly, they help prevent partial data updates, duplicate operations, unexpected commits, and production consistency issues.

The most important takeaway is this:

A reliable SAP Commerce implementation must control not just when data is saved, but also when it is committed, what happens when it fails, and how the application recovers safely.


SEO and publishing details

Primary keyword: SAP Commerce Transaction Management

Secondary keywords:

  • SAP Hybris transaction management

  • SAP Commerce @Transactional

  • SAP Commerce ModelService save transaction

  • SAP Commerce rollback

  • Spring TransactionTemplate

  • Transaction propagation in Java

  • SAP Commerce database deadlock

  • SAP Commerce transaction isolation

  • SAP Commerce batch processing

Featured image suggestion: A professional technical architecture illustration showing an SAP Commerce service layer, transaction boundary, ModelService, database commit and rollback paths, and an external API outside the database transaction.

Suggested internal links:

  • SAP Commerce ModelService Explained

  • SAP Commerce Interceptors Explained

  • SAP Commerce CronJobs Explained

  • SAP Commerce Business Process Engine

Suggested next article: SAP Commerce Cloud Caching Deep Dive: Cache Regions, Invalidation, Cluster Behavior, and Real-World Performance Troubleshooting.