# Transaction Retries using JavaEE and CDI with CMTs

In this post, we'll use the same concept as in [Transaction Retries using JavaEE and CDI with BMTs](https://blog.cloudneutral.se/transaction-retries-using-javaee-and-cdi-with-bmts). Only this time with container-managed transactions, or CMTs which is the default mode of operation with JTA.

# Transaction Retries in JavaEE

This article demonstrates an AOP-driven retry strategy for JavaEE apps using Stateless Session Beans with container-managed transactions (CMT), using the same stack and demo use case as in [Transaction Retries using JavaEE and CDI with BMTs](https://blog.cloudneutral.se/transaction-retries-using-javaee-and-cdi-with-bmts).

## Source Code

The source code for examples of this article can be found on [GitHub](https://github.com/kai-niemi/retry-demo/tree/main/retry-demo-javaee-cmt).

## What's the difference

To use bean-managed transactions, you would just add the `@TransactionManagement`annotation and set the transaction attributes accordingly:

```java
@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
@TransactionAttribute(REQUIRES_NEW)
public class OrderService {

    @TransactionBoundary
    public Order placeOrder(Order order) {
        Assert.isTrue(entityManager.isJoinedToTransaction(), "Expected transaction!");

        entityManager.persist(order);
        return order;
    }
}
```

With container-managed transactions (the default), you can either be explicit or leave out the `@TranactionManagement` annotation. Then add `@TransactionAttribute(NOT_SUPPORTED)`\` alongside `@TransactionBoundary` in the boundary methods. This will inform the container to not start a new transaction when invoking this method:

```java
@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class OrderService {

    @TransactionBoundary
    @TransactionAttribute(NOT_SUPPORTED)
    public Order placeOrder(Order order) {
        Assert.isTrue(entityManager.isJoinedToTransaction(), "Expected transaction!");

        entityManager.persist(order);
        return order;
    }
}
```

So the interesting question now is: How can that assertion still be true, given that NOT\_SUPPORTED propagation is used?

The answer sits in the `@TransactionBoundary` annotation which uses an `@InterceptorBinding` to wire in the `TransactionRetryInterceptor`, which in turn invokes a transaction service with `REQUIRES_NEW` propagation. The effect is that although it appears like that service boundary method is not transactional, it actually is but it's invocation is now deferred to a retry loop in the interceptor.

%[https://gist.github.com/kai-niemi/cd6b4182a596e8afe02fb6173221a739] 

This is a less intrusive approach to add retry logic to session beans and service activators (message listeners) when already invested in BMTs. No major refactoring efforts are needed.

## Demo

To try this out, we'll use the same order system designed to produce unrepeatable read (aka read/write) conflicts to activate the retry mechanism.

## Building

### Prerequisites

*   JDK8+ with 1.8 language level (OpenJDK compatible)
    
*   Maven 3+ (optional, embedded)
    
*   CockroachDB v22.1+ database
    

Install the JDK (Linux):

```bash
sudo apt-get -qq install -y openjdk-8-jdk
```

### Clone the project

```bash
git clone git@github.com/kai-niemi/retry-demo.git
cd retry-demo
```

### Build the project

```bash
chmod +x mvnw
./mvnw clean install
```

### Setup

Create the database:

```bash
cockroach sql --insecure --host=localhost -e "CREATE database orders"
```

Create the schema:

```bash
cockroach sql --insecure --host=locahlost --database orders  < src/resources/conf/create.sql
```

Start the app:

```bash
../mvnw clean install tomee:run
```

The default listen port is `8090` (can be changed in pom.xml):

## Usage

Open another shell and check that the service is up and connected to the DB:

```bash
curl http://localhost:8090/api
```

### Get Order Request Form

This prints out an order form template that we will use to create new orders:

```bash
curl http://localhost:8090/api/order/template| jq
```

Alternatively, pipe it to a file:

```bash
curl http://localhost:8090/api/order/template > form.json
```

### Submit Order Form

Create a new purchase order:

```bash
curl http://localhost:8090/api/order -i -X POST \
-H 'Content-Type: application/json' \
-d '{
    "billAddress": {
        "address1": "Street 1.1",
        "address2": "Street 1.2",
        "city": "City 1",
        "country": "Country 1",
        "postcode": "Code 1"
    },
    "customerId": -1,
    "deliveryAddress": {
        "address1": "Street 2.1",
        "address2": "Street 2.2",
        "city": "City 2",
        "country": "Country 2",
        "postcode": "Code 2"
    },
    "requestId": "bc3cba97-dee9-41b2-9110-2f5dfc2c5dae"
}'
```

Or using the file:

```bash
curl http://localhost:8090/api/order -H "Content-Type:application/json" -X POST \
-d "@form.json"
```

### Produce a Read/Write Conflict

Assuming we have an order with ID 1 in status `PLACED`. We will now read that order and change the status to something else by using concurrent transactions. This is known as the `unrepeatable read` conflict, prevented by 1SR from happening.

To have a predictable outcome, we'll use two sessions with a controllable delay between the read and write operations.

Overview of SQL operations:

```bash
select * from purchase_order where id=1; -- T1 
-- status is `PLACED`
wait 5s -- T1 
select * from purchase_order where id=1; -- T2
wait 5s -- T2
update status='CONFIRMED' where id=1; -- T1
update status='PAID' where id=1; -- T2
commit; -- T1
commit; -- T2 ERROR!
```

Prepare to run the first command:

```bash
curl http://localhost:8090/api/order/1?status=CONFIRMED\&delay=5000 -i -X PUT
```

Open another session, and prepare to run a similar command in less than 5sec after the first one:

```bash
curl http://localhost:8090/api/order/1?status=PAID\&delay=5000 -i -X PUT
```

When both commands are executed serially, it will cause a serialization conflict like this:

```bash
ERROR: restart transaction: TransactionRetryWithProtoRefreshError: WriteTooOldError: write for key /Table/109/1/12/0 at timestamp 1669990868.355588000,0 too old; wrote at 1669990868.778375000,3: "sql txn" meta={id=92409d02 key=/Table/109/1/12/0 pri=0.03022202 epo=0 ts=1669990868.778375000,3 min=1669990868.355588000,0 seq=0} lock=true stat=PENDING rts=1669990868.355588000,0 wto=false gul=1669990868.855588000,0
```

The interceptor will however catch this error since it has a state code `40001`, retry the business method and eventually succeed and deliver a `200 OK` to the client.

## Conclusion

In this article, we implemented a transaction retry strategy for JavaEE stateless session beans using container-managed transactions and a custom interceptor with interceptor bindings.

This reduces the amount of retry logic in ordinary service beans to simply add a `@TransactionBoundary` meta-annotation and change the transaction attribute to `@TransactionAttribute(NOT_SUPPORTED)`.
